mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-31 10:53:20 +08:00
[fix] improve iOS privacy and App Store distribution compliance
This commit is contained in:
@@ -15,6 +15,11 @@
|
||||
100000000000000000000006 /* NativeMetalVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 200000000000000000000009 /* NativeMetalVideoView.swift */; };
|
||||
100000000000000000000007 /* RemoteTouchInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20000000000000000000000A /* RemoteTouchInputView.swift */; };
|
||||
100000000000000000000008 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 20000000000000000000000D /* Assets.xcassets */; };
|
||||
100000000000000000000009 /* ThirdPartyLicensesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20000000000000000000000E /* ThirdPartyLicensesView.swift */; };
|
||||
10000000000000000000000A /* ThirdPartyLicenses.json in Resources */ = {isa = PBXBuildFile; fileRef = 20000000000000000000000F /* ThirdPartyLicenses.json */; };
|
||||
10000000000000000000000B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 200000000000000000000010 /* PrivacyInfo.xcprivacy */; };
|
||||
10000000000000000000000C /* OpenSSL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 200000000000000000000011 /* OpenSSL.framework */; };
|
||||
10000000000000000000000D /* OpenSSL.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 200000000000000000000011 /* OpenSSL.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -31,13 +36,30 @@
|
||||
20000000000000000000000B /* CrossDeskMobile.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CrossDeskMobile.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
20000000000000000000000C /* build_minirtc_ios.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build_minirtc_ios.sh; sourceTree = "<group>"; };
|
||||
20000000000000000000000D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
20000000000000000000000E /* ThirdPartyLicensesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartyLicensesView.swift; sourceTree = "<group>"; };
|
||||
20000000000000000000000F /* ThirdPartyLicenses.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Resources/ThirdPartyLicenses.json; sourceTree = "<group>"; };
|
||||
200000000000000000000010 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
200000000000000000000011 /* OpenSSL.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = OpenSSL.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
300000000000000000000002 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (10000000000000000000000D /* OpenSSL.framework in Embed Frameworks */, );
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
300000000000000000000001 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
10000000000000000000000C /* OpenSSL.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -59,6 +81,9 @@
|
||||
200000000000000000000001 /* CrossDeskMobileApp.swift */,
|
||||
200000000000000000000002 /* RemoteSessionModel.swift */,
|
||||
200000000000000000000003 /* ContentView.swift */,
|
||||
20000000000000000000000E /* ThirdPartyLicensesView.swift */,
|
||||
20000000000000000000000F /* ThirdPartyLicenses.json */,
|
||||
200000000000000000000010 /* PrivacyInfo.xcprivacy */,
|
||||
200000000000000000000004 /* RemoteSessionView.swift */,
|
||||
400000000000000000000003 /* Bridge */,
|
||||
400000000000000000000004 /* Rendering */,
|
||||
@@ -107,6 +132,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
20000000000000000000000B /* CrossDeskMobile.app */,
|
||||
200000000000000000000011 /* OpenSSL.framework */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -122,6 +148,7 @@
|
||||
600000000000000000000001 /* Sources */,
|
||||
300000000000000000000001 /* Frameworks */,
|
||||
600000000000000000000002 /* Resources */,
|
||||
300000000000000000000002 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -172,6 +199,8 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
100000000000000000000008 /* Assets.xcassets in Resources */,
|
||||
10000000000000000000000A /* ThirdPartyLicenses.json in Resources */,
|
||||
10000000000000000000000B /* PrivacyInfo.xcprivacy in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -193,10 +222,16 @@
|
||||
);
|
||||
outputPaths = (
|
||||
"$(PROJECT_DIR)/Vendor/iphoneos/$(CONFIGURATION)/libCrossDeskMiniRTC.a",
|
||||
"$(BUILT_PRODUCTS_DIR)/OpenSSL.framework/OpenSSL",
|
||||
"$(BUILT_PRODUCTS_DIR)/OpenSSL.framework/Info.plist",
|
||||
"$(BUILT_PRODUCTS_DIR)/OpenSSL.framework/PrivacyInfo.xcprivacy",
|
||||
"$(BUILT_PRODUCTS_DIR)/OpenSSL.framework/LICENSE.txt",
|
||||
"$(BUILT_PRODUCTS_DIR)/OpenSSL.framework/BuildMetadata.json",
|
||||
"$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/SourceMetadata.json",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/zsh;
|
||||
shellScript = "\"${PROJECT_DIR}/scripts/build_minirtc_ios.sh\"\n";
|
||||
shellScript = "set -e\npython3 \"${PROJECT_DIR}/scripts/prepare_source_metadata.py\" --output \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SourceMetadata.json\"\n\"${PROJECT_DIR}/scripts/build_minirtc_ios.sh\"\npython3 \"${PROJECT_DIR}/scripts/generate_third_party_licenses.py\" --inputs \"${PROJECT_DIR}/Vendor/iphoneos/${CONFIGURATION}/libCrossDeskMiniRTC.a.inputs.sha256\" --target-info \"${PROJECT_DIR}/Vendor/iphoneos/${CONFIGURATION}/minirtc-target-info.txt\" --xmake-version \"${PROJECT_DIR}/Vendor/iphoneos/${CONFIGURATION}/xmake-version.txt\" --xmake-repository \"$(cat \"${PROJECT_DIR}/Vendor/iphoneos/${CONFIGURATION}/xmake-repository.txt\")\"\npython3 \"${PROJECT_DIR}/scripts/package_openssl_ios.py\" --inputs \"${PROJECT_DIR}/Vendor/iphoneos/${CONFIGURATION}/libCrossDeskMiniRTC.a.inputs.sha256\" --output \"${BUILT_PRODUCTS_DIR}/OpenSSL.framework\"\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
@@ -208,6 +243,7 @@
|
||||
100000000000000000000001 /* CrossDeskMobileApp.swift in Sources */,
|
||||
100000000000000000000002 /* RemoteSessionModel.swift in Sources */,
|
||||
100000000000000000000003 /* ContentView.swift in Sources */,
|
||||
100000000000000000000009 /* ThirdPartyLicensesView.swift in Sources */,
|
||||
100000000000000000000004 /* RemoteSessionView.swift in Sources */,
|
||||
100000000000000000000005 /* CrossDeskRTCBridge.mm in Sources */,
|
||||
100000000000000000000006 /* NativeMetalVideoView.swift in Sources */,
|
||||
@@ -270,6 +306,7 @@
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = ("$(inherited)", "$(BUILT_PRODUCTS_DIR)", );
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/../../deps/submodules/minirtc/src/api",
|
||||
@@ -319,6 +356,7 @@
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = ("$(inherited)", "$(BUILT_PRODUCTS_DIR)", );
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/../../deps/submodules/minirtc/src/api",
|
||||
|
||||
@@ -11,6 +11,7 @@ typedef NS_ENUM(NSInteger, CrossDeskSignalState) {
|
||||
CrossDeskSignalStateReconnecting,
|
||||
CrossDeskSignalStateServerClosed,
|
||||
CrossDeskSignalStateTLSCertificateError,
|
||||
CrossDeskSignalStateCredentialUnavailable,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CrossDeskConnectionState) {
|
||||
@@ -47,6 +48,8 @@ typedef NS_ENUM(NSInteger, CrossDeskVideoAdaptationPolicy) {
|
||||
@optional
|
||||
- (void)rtcBridge:(CrossDeskRTCBridge *)bridge
|
||||
didChangeSignalState:(CrossDeskSignalState)state;
|
||||
- (void)rtcBridge:(CrossDeskRTCBridge *)bridge
|
||||
didChangeIdentityStorageError:(BOOL)hasError;
|
||||
- (void)rtcBridge:(CrossDeskRTCBridge *)bridge
|
||||
didChangeConnectionState:(CrossDeskConnectionState)state
|
||||
remoteID:(NSString *)remoteID;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#import "CrossDeskRTCBridge.h"
|
||||
|
||||
#import <CoreMedia/CoreMedia.h>
|
||||
#import <Security/Security.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -101,6 +102,113 @@ bool IsReusableIdentity(const std::string &identity) {
|
||||
separator + 1 < identity.size();
|
||||
}
|
||||
|
||||
std::string ProvisionedIdentityCredential(const std::string &received,
|
||||
const std::string ¤t) {
|
||||
// A successful re-login can acknowledge the ID without echoing its password.
|
||||
// Retain the credential used to authenticate that same ID.
|
||||
if (!IsReusableIdentity(received) && IsReusableIdentity(current) &&
|
||||
current.substr(0, current.find('@')) == received) return current;
|
||||
return received;
|
||||
}
|
||||
|
||||
NSString *IdentityCredentialAccount(NSString *host, NSInteger port) {
|
||||
// Preserve the old server-scoped key so upgrades retain the same identity.
|
||||
return [NSString stringWithFormat:@"CrossDeskIdentity.%@.%ld", host, (long)port];
|
||||
}
|
||||
|
||||
NSMutableDictionary *IdentityCredentialQuery(NSString *account) {
|
||||
return [@{
|
||||
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
||||
(__bridge id)kSecAttrService: @"cn.crossdesk.mobile.signaling-identity",
|
||||
(__bridge id)kSecAttrAccount: account,
|
||||
(__bridge id)kSecAttrSynchronizable: @NO,
|
||||
} mutableCopy];
|
||||
}
|
||||
|
||||
bool IsReusableIdentityValue(NSString *value) {
|
||||
return [value isKindOfClass:NSString.class] && value.UTF8String &&
|
||||
IsReusableIdentity(std::string(value.UTF8String));
|
||||
}
|
||||
|
||||
OSStatus SaveIdentityCredential(NSString *account, NSString *value) {
|
||||
if (!IsReusableIdentityValue(value)) return errSecParam;
|
||||
NSMutableDictionary *query = IdentityCredentialQuery(account);
|
||||
NSDictionary *attributes = @{
|
||||
(__bridge id)kSecValueData: [value dataUsingEncoding:NSUTF8StringEncoding],
|
||||
(__bridge id)kSecAttrAccessible:
|
||||
(__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
||||
};
|
||||
// Update in place: deleting first could lose a valid credential on failure.
|
||||
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query,
|
||||
(__bridge CFDictionaryRef)attributes);
|
||||
if (status == errSecItemNotFound) {
|
||||
NSMutableDictionary *item = [query mutableCopy];
|
||||
[item addEntriesFromDictionary:attributes];
|
||||
status = SecItemAdd((__bridge CFDictionaryRef)item, nullptr);
|
||||
if (status == errSecDuplicateItem) {
|
||||
status = SecItemUpdate((__bridge CFDictionaryRef)query,
|
||||
(__bridge CFDictionaryRef)attributes);
|
||||
}
|
||||
}
|
||||
if (status == errSecSuccess) {
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:account];
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
OSStatus LoadIdentityCredential(NSString *account, NSString **value) {
|
||||
*value = nil;
|
||||
NSMutableDictionary *query = IdentityCredentialQuery(account);
|
||||
query[(__bridge id)kSecReturnData] = @YES;
|
||||
query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
|
||||
CFTypeRef result = nullptr;
|
||||
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
|
||||
id object = CFBridgingRelease(result);
|
||||
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
|
||||
if (status == errSecSuccess) {
|
||||
NSString *stored = [object isKindOfClass:NSData.class]
|
||||
? [[NSString alloc] initWithData:object encoding:NSUTF8StringEncoding] : nil;
|
||||
if (!IsReusableIdentityValue(stored)) return errSecDecode;
|
||||
*value = stored;
|
||||
// A Keychain value wins over a stale copy left by an interrupted migration.
|
||||
[defaults removeObjectForKey:account];
|
||||
return errSecSuccess;
|
||||
}
|
||||
if (status != errSecItemNotFound) return status;
|
||||
|
||||
NSString *legacy = [defaults objectForKey:account];
|
||||
if (!legacy) return errSecItemNotFound;
|
||||
if (!IsReusableIdentityValue(legacy)) {
|
||||
// Older passwordless IDs cannot authenticate on a subsequent app launch.
|
||||
[defaults removeObjectForKey:account];
|
||||
return errSecItemNotFound;
|
||||
}
|
||||
status = SaveIdentityCredential(account, legacy);
|
||||
if (status == errSecSuccess) *value = legacy;
|
||||
return status;
|
||||
}
|
||||
|
||||
OSStatus MigrateLegacyIdentityCredentials() {
|
||||
OSStatus first_error = errSecSuccess;
|
||||
for (NSString *key in NSUserDefaults.standardUserDefaults.dictionaryRepresentation) {
|
||||
if (![key hasPrefix:@"CrossDeskIdentity."]) continue;
|
||||
NSString *unused = nil;
|
||||
OSStatus status = LoadIdentityCredential(key, &unused);
|
||||
if (status != errSecSuccess && status != errSecItemNotFound &&
|
||||
first_error == errSecSuccess) first_error = status;
|
||||
}
|
||||
return first_error;
|
||||
}
|
||||
|
||||
OSStatus RemoveIdentityCredential(NSString *account) {
|
||||
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)IdentityCredentialQuery(account));
|
||||
if (status == errSecSuccess || status == errSecItemNotFound) {
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:account];
|
||||
return errSecSuccess;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void CopyCString(char *destination, size_t capacity,
|
||||
const std::string &source) {
|
||||
if (!destination || capacity == 0) return;
|
||||
@@ -230,6 +338,7 @@ void DispatchMain(dispatch_block_t block) {
|
||||
- (void)createIdentityPeer;
|
||||
- (void)createControllerPeer;
|
||||
- (void)destroyIdentityPeer;
|
||||
- (void)reportIdentityStorageStatus:(OSStatus)status;
|
||||
- (void)destroyControllerPeer;
|
||||
@end
|
||||
|
||||
@@ -455,7 +564,16 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
const bool unchanged = self->_state->signal_host == host_value &&
|
||||
self->_state->signal_port == signalPort &&
|
||||
self->_state->identity_peer != nullptr;
|
||||
if (unchanged) return;
|
||||
const OSStatus migration_status = MigrateLegacyIdentityCredentials();
|
||||
[self reportIdentityStorageStatus:migration_status];
|
||||
if (unchanged) {
|
||||
if (IsReusableIdentity(self->_state->identity_with_password)) {
|
||||
NSString *value = [NSString stringWithUTF8String:self->_state->identity_with_password.c_str()];
|
||||
[self reportIdentityStorageStatus:SaveIdentityCredential(
|
||||
IdentityCredentialAccount(trimmed, signalPort), value)];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
[self destroyControllerPeer];
|
||||
[self destroyIdentityPeer];
|
||||
@@ -467,29 +585,26 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
self->_state->identity_with_password.clear();
|
||||
self->_state->identity_base.clear();
|
||||
|
||||
NSString *key = [NSString stringWithFormat:@"CrossDeskIdentity.%@.%ld",
|
||||
trimmed, (long)signalPort];
|
||||
NSString *cached = [[NSUserDefaults standardUserDefaults]
|
||||
stringForKey:key];
|
||||
if (cached.length > 0) {
|
||||
const std::string cached_identity = cached.UTF8String ?: "";
|
||||
if (IsReusableIdentity(cached_identity)) {
|
||||
self->_state->identity_with_password = cached_identity;
|
||||
self->_state->identity_base = BaseIdentity(cached_identity);
|
||||
DispatchMain(^{
|
||||
id<CrossDeskRTCBridgeDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:
|
||||
@selector(rtcBridge:didProvisionIdentity:)]) {
|
||||
[delegate rtcBridge:self didProvisionIdentity:cached];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// A bare ID cannot authenticate after its first signaling session.
|
||||
// Older iOS builds stored it anyway, causing every later launch to be
|
||||
// rejected as "Incorrect password". Drop it and request a fresh
|
||||
// server-issued ID/password pair.
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
|
||||
}
|
||||
NSString *cached = nil;
|
||||
OSStatus load_status = LoadIdentityCredential(
|
||||
IdentityCredentialAccount(trimmed, signalPort), &cached);
|
||||
if (load_status == errSecSuccess) {
|
||||
self->_state->identity_with_password = cached.UTF8String;
|
||||
self->_state->identity_base = BaseIdentity(self->_state->identity_with_password);
|
||||
NSString *identifier = [NSString stringWithUTF8String:self->_state->identity_base.c_str()];
|
||||
DispatchMain(^{
|
||||
id<CrossDeskRTCBridgeDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(rtcBridge:didProvisionIdentity:)]) {
|
||||
[delegate rtcBridge:self didProvisionIdentity:identifier];
|
||||
}
|
||||
});
|
||||
} else if (load_status != errSecItemNotFound) {
|
||||
[self reportIdentityStorageStatus:load_status];
|
||||
// Locked/unavailable Keychain is not the same as a missing identity.
|
||||
[self handleSignalState:CrossDeskSignalStateCredentialUnavailable
|
||||
role:PeerRole::Identity
|
||||
generation:self->_identityGeneration.load()];
|
||||
return;
|
||||
}
|
||||
[self createIdentityPeer];
|
||||
});
|
||||
@@ -842,6 +957,20 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
});
|
||||
}
|
||||
|
||||
- (void)reportIdentityStorageStatus:(OSStatus)status {
|
||||
const BOOL has_error = status != errSecSuccess;
|
||||
if (has_error) {
|
||||
// Log only the OSStatus, never an account, credential or Keychain payload.
|
||||
NSLog(@"CrossDesk identity Keychain operation failed (%d)", (int)status);
|
||||
}
|
||||
DispatchMain(^{
|
||||
id<CrossDeskRTCBridgeDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(rtcBridge:didChangeIdentityStorageError:)]) {
|
||||
[delegate rtcBridge:self didChangeIdentityStorageError:has_error];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)createIdentityPeer {
|
||||
if (_state->identity_peer || _state->signal_host.empty()) return;
|
||||
_state->identity_context.generation = _identityGeneration.fetch_add(1) + 1;
|
||||
@@ -962,10 +1091,16 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
self->_state->identity_recovery_attempted = true;
|
||||
NSString *host = [NSString stringWithUTF8String:
|
||||
self->_state->signal_host.c_str()];
|
||||
NSString *key = [NSString stringWithFormat:@"CrossDeskIdentity.%@.%d",
|
||||
host,
|
||||
self->_state->signal_port];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
|
||||
OSStatus status = RemoveIdentityCredential(
|
||||
IdentityCredentialAccount(host, self->_state->signal_port));
|
||||
[self reportIdentityStorageStatus:status];
|
||||
if (status != errSecSuccess) {
|
||||
[self destroyIdentityPeer];
|
||||
[self handleSignalState:CrossDeskSignalStateCredentialUnavailable
|
||||
role:PeerRole::Identity
|
||||
generation:self->_identityGeneration.load()];
|
||||
return;
|
||||
}
|
||||
[self destroyIdentityPeer];
|
||||
self->_state->identity_with_password.clear();
|
||||
self->_state->identity_base.clear();
|
||||
@@ -1078,25 +1213,25 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
const std::string identity_copy = identity;
|
||||
dispatch_async(_rtcQueue, ^{
|
||||
if (generation != self->_identityGeneration.load()) return;
|
||||
self->_state->identity_with_password = identity_copy;
|
||||
self->_state->identity_base = BaseIdentity(identity_copy);
|
||||
const std::string credential = ProvisionedIdentityCredential(
|
||||
identity_copy, self->_state->identity_with_password);
|
||||
self->_state->identity_with_password = credential;
|
||||
self->_state->identity_base = BaseIdentity(credential);
|
||||
NSString *host = [NSString stringWithUTF8String:
|
||||
self->_state->signal_host.c_str()];
|
||||
NSString *key = [NSString stringWithFormat:@"CrossDeskIdentity.%@.%d",
|
||||
host, self->_state->signal_port];
|
||||
NSString *value = [NSString stringWithUTF8String:identity_copy.c_str()];
|
||||
if (IsReusableIdentity(identity_copy)) {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
|
||||
} else {
|
||||
// Keep a passwordless identity only for this process. It is valid for
|
||||
// the current login but is not safe to reuse on the next app launch.
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
|
||||
}
|
||||
NSString *account = IdentityCredentialAccount(host, self->_state->signal_port);
|
||||
NSString *value = [NSString stringWithUTF8String:credential.c_str()];
|
||||
OSStatus status = IsReusableIdentity(credential)
|
||||
? SaveIdentityCredential(account, value)
|
||||
: RemoveIdentityCredential(account);
|
||||
[self reportIdentityStorageStatus:status];
|
||||
// Only the public device ID crosses into UI state; keep its password here.
|
||||
NSString *identifier = [NSString stringWithUTF8String:self->_state->identity_base.c_str()];
|
||||
DispatchMain(^{
|
||||
if (generation != self->_identityGeneration.load()) return;
|
||||
id<CrossDeskRTCBridgeDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(rtcBridge:didProvisionIdentity:)]) {
|
||||
[delegate rtcBridge:self didProvisionIdentity:value];
|
||||
[delegate rtcBridge:self didProvisionIdentity:identifier];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -711,6 +711,10 @@ private struct ServerSettingsView: View {
|
||||
} header: {
|
||||
Text("服务器")
|
||||
} footer: {
|
||||
if let warning = session.identityStorageWarning {
|
||||
Text(warning)
|
||||
.foregroundStyle(.orange)
|
||||
}
|
||||
Text(session.localIdentity.isEmpty
|
||||
? "正在获取本机 ID…"
|
||||
: "本机 ID \(session.localIdentity)")
|
||||
@@ -720,6 +724,20 @@ private struct ServerSettingsView: View {
|
||||
.padding(.top, 16)
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
Section {
|
||||
if let source = SourceMetadata.bundled {
|
||||
Link(destination: source.privacyPolicyURL) {
|
||||
Label("隐私政策", systemImage: "hand.raised")
|
||||
}
|
||||
}
|
||||
NavigationLink {
|
||||
ThirdPartyLicensesView()
|
||||
} label: {
|
||||
Label("关于与开源许可", systemImage: "doc.text")
|
||||
}
|
||||
} footer: {
|
||||
Text("本应用使用 OpenFEC,遵循 CeCILL-C 许可。版权声明、许可全文和源码入口见“关于与开源许可”。")
|
||||
}
|
||||
}
|
||||
.navigationTitle("设置")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
<string>3B52.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
<string>8FFB.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -332,6 +332,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
@Published private(set) var signalStatus = "正在连接信令服务"
|
||||
@Published private(set) var connectionStatus = "未连接"
|
||||
@Published private(set) var localIdentity = ""
|
||||
@Published private(set) var identityStorageWarning: String?
|
||||
@Published private(set) var isConnecting = false
|
||||
@Published private(set) var isConnected = false
|
||||
@Published private(set) var sessionVisible = false
|
||||
@@ -828,6 +829,12 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
bridge.sendWindowsKeyCode(keyCode, isDown: isDown)
|
||||
}
|
||||
|
||||
func rtcBridge(_ bridge: CrossDeskRTCBridge, didChangeIdentityStorageError hasError: Bool) {
|
||||
identityStorageWarning = hasError
|
||||
? "本机登录凭据暂时无法安全存取。请解锁设备后,在设置中点击“应用”重试。"
|
||||
: nil
|
||||
}
|
||||
|
||||
func rtcBridge(_ bridge: CrossDeskRTCBridge,
|
||||
didChange state: CrossDeskSignalState) {
|
||||
signalConnected = state.rawValue == 1
|
||||
@@ -840,6 +847,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
case 4: signalStatus = "信令服务重连中"
|
||||
case 5: signalStatus = "信令服务器已关闭连接"
|
||||
case 6: signalStatus = "TLS 证书校验失败"
|
||||
case 7: signalStatus = "无法访问本机登录凭据,请解锁后重试"
|
||||
default: signalStatus = "未知信令状态"
|
||||
}
|
||||
if signalConnected {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,185 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
private struct LicenseDocument: Decodable, Identifiable {
|
||||
let title: String
|
||||
let text: String
|
||||
var id: String { title }
|
||||
}
|
||||
|
||||
private struct LicensedComponent: Decodable, Identifiable {
|
||||
let id: String
|
||||
let name: String
|
||||
let version: String
|
||||
let license: String
|
||||
let sourceURL: URL
|
||||
let buildSourceURL: URL?
|
||||
let documents: [LicenseDocument]
|
||||
|
||||
var displayVersion: String {
|
||||
if id == "crossdesk" {
|
||||
return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? ""
|
||||
}
|
||||
return version.count == 40 ? String(version.prefix(12)) : version
|
||||
}
|
||||
}
|
||||
|
||||
private struct LicenseCatalog: Decodable {
|
||||
let schemaVersion: Int
|
||||
let components: [LicensedComponent]
|
||||
|
||||
static let bundled: Result<LicenseCatalog, Error> = Result {
|
||||
guard let url = Bundle.main.url(forResource: "ThirdPartyLicenses", withExtension: "json") else {
|
||||
throw CocoaError(.fileNoSuchFile)
|
||||
}
|
||||
let catalog = try JSONDecoder().decode(LicenseCatalog.self, from: Data(contentsOf: url))
|
||||
guard catalog.schemaVersion == 1, catalog.components.contains(where: { $0.id == "openfec" }) else {
|
||||
throw CocoaError(.fileReadCorruptFile)
|
||||
}
|
||||
return catalog
|
||||
}
|
||||
}
|
||||
|
||||
struct SourceMetadata: Decodable {
|
||||
let revision: String
|
||||
let tag: String?
|
||||
let isModified: Bool
|
||||
let sourceURL: URL
|
||||
let buildInstructionsURL: URL
|
||||
let privacyPolicyURL: URL
|
||||
|
||||
static let bundled: SourceMetadata? = {
|
||||
guard let url = Bundle.main.url(forResource: "SourceMetadata", withExtension: "json"),
|
||||
let data = try? Data(contentsOf: url) else { return nil }
|
||||
return try? JSONDecoder().decode(SourceMetadata.self, from: data)
|
||||
}()
|
||||
}
|
||||
|
||||
struct ThirdPartyLicensesView: View {
|
||||
var body: some View {
|
||||
Group {
|
||||
switch LicenseCatalog.bundled {
|
||||
case .success(let catalog):
|
||||
List {
|
||||
Section("本版本源码与构建说明") {
|
||||
if let source = SourceMetadata.bundled {
|
||||
Text(source.tag ?? String(source.revision.prefix(12)))
|
||||
.textSelection(.enabled)
|
||||
if source.isModified {
|
||||
Text("开发版本包含尚未发布的修改,以下链接为基础版本源码。")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Link("CrossDesk 源码", destination: source.sourceURL)
|
||||
Link("获取源码、修改与重新构建", destination: source.buildInstructionsURL)
|
||||
Link("隐私政策", destination: source.privacyPolicyURL)
|
||||
}
|
||||
Text("各组件的使用、复制、修改和再分发权利以相应开源许可证为准。普通使用条款不限制这些许可证授予的权利。")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Section("OpenFEC 告知") {
|
||||
Text("本应用使用 OpenFEC \(catalog.components.first(where: { $0.id == "openfec" })?.displayVersion ?? "") 提供前向纠错功能,遵循 CeCILL-C 1.0 许可。")
|
||||
Text("(c) Copyright 2009 - 2012 INRIA - All rights reserved")
|
||||
.font(.footnote)
|
||||
Text("OpenFEC 按其许可提供有限保证,作者、权利人和后续许可人的责任亦受限制。完整声明、许可条款及源码见下方。")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
if let openFEC = catalog.components.first(where: { $0.id == "openfec" }) {
|
||||
NavigationLink("OpenFEC 许可与源码") {
|
||||
ComponentLicenseView(component: openFEC)
|
||||
}
|
||||
}
|
||||
}
|
||||
Section {
|
||||
ForEach(catalog.components) { component in
|
||||
NavigationLink {
|
||||
ComponentLicenseView(component: component)
|
||||
} label: {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(component.name)
|
||||
Text(component.displayVersion + " · " + component.license)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("开源组件")
|
||||
} footer: {
|
||||
Text("许可全文和版权声明可离线阅读;查看源码需要网络连接。")
|
||||
}
|
||||
}
|
||||
case .failure:
|
||||
VStack(spacing: 12) {
|
||||
Image(systemName: "doc.text.magnifyingglass")
|
||||
.font(.largeTitle)
|
||||
Text("无法读取开源许可")
|
||||
.font(.headline)
|
||||
Text("应用中的许可文件缺失或损坏,请重新安装应用。")
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.navigationTitle("关于与开源许可")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
|
||||
private struct ComponentLicenseView: View {
|
||||
let component: LicensedComponent
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
Text(component.name).font(.headline)
|
||||
Text(component.displayVersion).textSelection(.enabled)
|
||||
Text(component.license).foregroundStyle(.secondary)
|
||||
Link("查看源码", destination: component.id == "crossdesk"
|
||||
? SourceMetadata.bundled?.sourceURL ?? component.sourceURL
|
||||
: component.sourceURL)
|
||||
if let buildSourceURL = component.buildSourceURL {
|
||||
Link("构建配方与项目补丁", destination: buildSourceURL)
|
||||
}
|
||||
}
|
||||
Section("许可与版权声明") {
|
||||
ForEach(component.documents) { document in
|
||||
NavigationLink {
|
||||
LicenseTextView(text: document.text)
|
||||
.navigationTitle(document.title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
} label: {
|
||||
Text(document.title == "Source copyright and license notices"
|
||||
? "源码版权与许可声明" : document.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(component.name)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
|
||||
// UITextView keeps long license documents selectable and efficient to scroll.
|
||||
private struct LicenseTextView: UIViewRepresentable {
|
||||
let text: String
|
||||
|
||||
func makeUIView(context: Context) -> UITextView {
|
||||
let view = UITextView()
|
||||
view.isEditable = false
|
||||
view.isSelectable = true
|
||||
view.dataDetectorTypes = [.link]
|
||||
view.adjustsFontForContentSizeCategory = true
|
||||
view.font = .preferredFont(forTextStyle: .body)
|
||||
view.textColor = .label
|
||||
view.backgroundColor = .systemBackground
|
||||
view.textContainerInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ view: UITextView, context: Context) {
|
||||
if view.text != text { view.text = text }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key>
|
||||
<string>app-store-connect</string>
|
||||
<key>destination</key>
|
||||
<string>export</string>
|
||||
<key>signingStyle</key>
|
||||
<string>automatic</string>
|
||||
<key>manageAppVersionAndBuildNumber</key>
|
||||
<false/>
|
||||
<key>uploadSymbols</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
+174
-2
@@ -7,7 +7,7 @@ There is no `WKWebView` or browser runtime.
|
||||
## Requirements
|
||||
|
||||
- Xcode 16 or newer
|
||||
- Xmake available on `PATH`
|
||||
- Xmake 3.1.1 available on `PATH` (or selected with `XMAKE_BIN`)
|
||||
- A physical arm64 iPhone or iPad running iOS 16 or newer
|
||||
|
||||
## Build
|
||||
@@ -22,6 +22,28 @@ cd crossdesk
|
||||
|
||||
For an existing checkout, run `git submodule update --init --recursive` from
|
||||
the repository root. All remaining commands below use that root directory.
|
||||
For a released app, select the tag shown in **Settings → About and open-source
|
||||
licenses** when cloning: `git clone --branch <source-tag> --recurse-submodules
|
||||
https://github.com/kunkundi/crossdesk.git`. The iOS marketing version is independent
|
||||
of the repository tag; use the source tag or commit recorded in the app.
|
||||
|
||||
Install [Xmake 3.1.1](https://github.com/xmake-io/xmake/releases/tag/v3.1.1),
|
||||
CMake, and a current Python 3 with setuptools/wheel. Pin the package repository
|
||||
in a separate Xmake global directory before building:
|
||||
|
||||
```sh
|
||||
export XMAKE_GLOBALDIR="$HOME/.cache/crossdesk/xmake-ios"
|
||||
xmake repo --update
|
||||
git -C "$XMAKE_GLOBALDIR/.xmake/repositories/xmake-repo" fetch origin 121109352da272cd1a3d4ed52f2dcda34f839955
|
||||
git -C "$XMAKE_GLOBALDIR/.xmake/repositories/xmake-repo" checkout --detach 121109352da272cd1a3d4ed52f2dcda34f839955
|
||||
```
|
||||
|
||||
Keep `XMAKE_GLOBALDIR` set for command-line Xcode builds. For builds launched
|
||||
from the Xcode app, set it as a user-defined build setting on the target, or
|
||||
pin the default `~/.xmake/repositories/xmake-repo` instead. The build checks
|
||||
the tool version, package repository commit and reviewed recipe hashes.
|
||||
Application dependencies are built from source, with Xmake precompiled
|
||||
package downloads disabled.
|
||||
|
||||
1. Open `apps/ios/CrossDeskMobile.xcodeproj` and select the `CrossDeskMobile`
|
||||
target and scheme.
|
||||
@@ -34,7 +56,8 @@ the repository root. All remaining commands below use that root directory.
|
||||
|
||||
The first build phase builds MiniRTC and the CrossDesk wire library, downloads
|
||||
and builds their dependencies, then merges the iPhoneOS archives into a local library under
|
||||
`Vendor/`. This build path targets physical devices; simulator builds are
|
||||
`Vendor/`. OpenSSL is kept separate and packaged as a static framework with SDK
|
||||
privacy resources in the Xcode products directory. This build path targets physical devices; simulator builds are
|
||||
not supported. The exact tool versions used by CI are recorded in the
|
||||
[workflow](../../.github/workflows/build.yml).
|
||||
|
||||
@@ -67,6 +90,74 @@ decoding is currently unsupported. Desktop builds retain OpenH264.
|
||||
The native build script checks the merged archive for OpenH264 entry points
|
||||
and rejects a build if they are present, including in cached archives.
|
||||
|
||||
## Third-party licenses
|
||||
|
||||
**设置 → 关于与开源许可** provides offline license texts, copyright notices,
|
||||
and source links. OpenFEC is acknowledged both on the settings page and at the
|
||||
top of the notices page, with its INRIA copyright, CeCILL-C terms and limited
|
||||
warranty/liability notice. Its embedded BSD and CC-BY-SA notices are preserved
|
||||
in the source-notices document as well.
|
||||
|
||||
`licenses/sources.json` pins the reviewed source archives (SHA-256), Git
|
||||
revisions, selected legal files and source-notice extraction rules.
|
||||
`CrossDeskMobile/Resources/ThirdPartyLicenses.json` is the generated resource
|
||||
bundled by Xcode. The catalog covers 34 components for the default iOS build,
|
||||
including header-only dependencies, GLib's proxy-libintl, spdlog's bundled fmt,
|
||||
WebRTC-derived code, inih, and the applicable patent notices. Host build tools,
|
||||
OpenH264, desktop-only libraries, and the disabled libpsl built-in suffix list
|
||||
are excluded.
|
||||
|
||||
Regenerate from the pinned source distributions with Python 3.9 or newer:
|
||||
|
||||
```sh
|
||||
python3 apps/ios/scripts/generate_third_party_licenses.py --generate
|
||||
```
|
||||
|
||||
The collector uses original checksum-verified archives, including GLib's
|
||||
`COPYING` file even when Xmake excludes it from an extracted build directory.
|
||||
It reuses matching Xmake downloads or caches downloads outside the repository
|
||||
in `~/.cache/crossdesk/licenses/`. License text is preserved, with explicit
|
||||
character decoding for the legacy OpenFEC files. Copyright comment blocks are
|
||||
collected from the reviewed runtime source directories; the catalog is an
|
||||
attribution superset, not a claim that every source file survives linker
|
||||
dead stripping.
|
||||
|
||||
Normal Xcode builds verify the checked-in resource offline, compare all resolved
|
||||
dependency versions (including header-only packages) with the catalog, check
|
||||
the merged archive's input hashes and local package recipes, and check the
|
||||
MiniRTC revision. A missing, stale or altered catalog fails the build. When
|
||||
dependencies or MiniRTC change, review the corresponding source notices and
|
||||
update the lock before regenerating; regeneration alone does not select a new
|
||||
dependency version. `MINIRTC_ENABLE_AOM=true` also requires a reviewed AOM entry.
|
||||
|
||||
The source screen links to the exact application tag/commit, MiniRTC commit,
|
||||
and custom dependency recipes/patches. `SourceMetadata.json` is generated in
|
||||
the app bundle, not in the checkout. Development builds with local edits are
|
||||
explicitly identified as modified. Version-tag CI builds reject an unclean
|
||||
checkout or a mismatched tag. The release archive also carries
|
||||
`ThirdPartySources.json`, `SourceMetadata.json` and the open-source rights
|
||||
notice; release notes provide the corresponding source and build links.
|
||||
Keep the source tags, submodule commits and referenced dependency source
|
||||
archives available for recipients of that release.
|
||||
|
||||
To change a library, edit MiniRTC in its submodule, or modify the relevant
|
||||
package recipe/patch under `deps/submodules/minirtc/thirdparty`. If a package
|
||||
recipe changes, change its package configuration revision (or rebuild that
|
||||
package) so Xmake does not reuse a previous binary. Review affected notices,
|
||||
update recipe hashes, commit MiniRTC, update its revision in `sources.json`,
|
||||
then regenerate the catalog. Build the application again to relink it with
|
||||
the modified library. Use your own team and bundle identifier to install the
|
||||
modified build; the maintainer's signing credentials are not required.
|
||||
|
||||
These materials implement source identification and attribution. They do not
|
||||
by themselves resolve GPL/LGPL distribution-term compatibility or grant
|
||||
additional permissions from other copyright holders. Applicable modification,
|
||||
reverse-engineering and installation-information rights must also be respected.
|
||||
See [LGPLv3 section 4](https://www.gnu.org/licenses/lgpl-3.0.html) and
|
||||
[GPLv3 section 6](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
|
||||
## Optional codecs
|
||||
|
||||
The unused libaom backends are excluded by default. To include them for development,
|
||||
set the `MINIRTC_ENABLE_AOM` environment variable (or Xcode user-defined build setting)
|
||||
to `true`. This includes libaom in the merged native archive without changing the
|
||||
@@ -106,3 +197,84 @@ If decoded frames do not reach the bridge, check that the selected stream is
|
||||
named `Display1`, `Display2`, and so on. If no frames decode, check the selected
|
||||
codec and the desktop capture/encoding logs, then reconnect or switch displays
|
||||
to request a new key frame.
|
||||
|
||||
## App Store archive and bundle validation
|
||||
|
||||
OpenSSL is on Apple's
|
||||
[required SDK privacy-manifest list](https://developer.apple.com/support/third-party-SDK-requirements/).
|
||||
The iOS build compiles OpenSSL from source, packages it separately as
|
||||
`OpenSSL.framework`, then links and embeds it using Xcode's
|
||||
[static framework support](https://developer.apple.com/documentation/xcode/creating-a-static-framework).
|
||||
Xcode removes the static archive from the embedded framework (it may insert a
|
||||
small resource-framework stub) and preserves `PrivacyInfo.xcprivacy`, source
|
||||
metadata and license texts. The application does not load a dynamic OpenSSL copy.
|
||||
|
||||
The iOS-specific OpenSSL recipe disables POSIX metadata calls, automatic
|
||||
`openssl.cnf` loading, and the unused file-URI store provider. It selects the
|
||||
upstream `getrandom` entropy backend, which uses Darwin's `getentropy` on the
|
||||
supported iOS versions; the random-device fallback is not compiled. Existing
|
||||
in-memory certificate/key handling remains available, while `OSSL_STORE` file
|
||||
URLs are intentionally unsupported. Signaling trust and hostname validation
|
||||
use Apple Security rather than OpenSSL's build-host certificate paths.
|
||||
Desktop OpenSSL builds are unchanged.
|
||||
|
||||
`package_openssl_ios.py` checks the actual OpenSSL archives for file metadata,
|
||||
disk-space and boot-time API imports before producing the framework. Its
|
||||
`sysctlbyname` calls are limited to the reviewed `armcap.c` CPU implementation
|
||||
selection, not fingerprinting. The SDK privacy manifest declares no tracking,
|
||||
SDK data collection or required-reason API use for this specific configuration.
|
||||
This is not a declaration about generic OpenSSL builds or the application's
|
||||
signaling/relay data processing. Source or configuration changes require another
|
||||
review; the packager rejects known forbidden imports even when reusing a cache.
|
||||
|
||||
`PrivacyInfo.xcprivacy` declares app preferences (`CA92.1`), sandbox and
|
||||
user-selected file metadata (`C617.1`, `3B52.1`), and elapsed/event timing
|
||||
(`35F9.1`, `8FFB.1`). MiniRTC uses timing for pacing, timeouts, RTT and media
|
||||
packet timestamps. The iOS GLib recipe disables unused filesystem-capacity
|
||||
and system-volume discovery. These APIs must not be repurposed for fingerprinting.
|
||||
The app-level manifest does not assert that the service collects no data: App Store
|
||||
Connect privacy answers also depend on actual signaling/relay-server practices.
|
||||
The application links to the [privacy policy](../../PRIVACY.md), including iOS
|
||||
clipboard, file, local thumbnail and Keychain behavior.
|
||||
|
||||
Validate a built bundle using:
|
||||
|
||||
```sh
|
||||
python3 apps/ios/scripts/verify_app_bundle.py /path/to/CrossDeskMobile.app
|
||||
```
|
||||
|
||||
For a release, first commit all application and submodule changes, regenerate
|
||||
notices for the committed dependency revision, and check out the intended
|
||||
version tag. Publish the tag and its referenced MiniRTC commit to the public repositories
|
||||
before distributing the app. Local tag verification cannot prove that recipients
|
||||
can fetch an unpublished commit. With an App Store-capable development team
|
||||
configured in Xcode:
|
||||
|
||||
```sh
|
||||
xcodebuild -project apps/ios/CrossDeskMobile.xcodeproj \
|
||||
-scheme CrossDeskMobile -configuration Release \
|
||||
-destination 'generic/platform=iOS' \
|
||||
-archivePath /tmp/CrossDeskMobile.xcarchive \
|
||||
CROSSDESK_RELEASE_BUILD=YES archive
|
||||
|
||||
python3 apps/ios/scripts/verify_app_bundle.py \
|
||||
/tmp/CrossDeskMobile.xcarchive/Products/Applications/CrossDeskMobile.app \
|
||||
--require-release
|
||||
|
||||
xcodebuild -exportArchive -archivePath /tmp/CrossDeskMobile.xcarchive \
|
||||
-exportOptionsPlist apps/ios/ExportOptions-AppStore.plist \
|
||||
-exportPath /tmp/CrossDeskMobile-AppStore
|
||||
```
|
||||
|
||||
Set a new `CURRENT_PROJECT_VERSION` for each upload. The export configuration
|
||||
creates a local App Store export; it does not submit the app. Distribution
|
||||
signing requires the appropriate Apple Developer Program membership and
|
||||
provisioning. The unsigned GitHub artifact is for development, not an App Store
|
||||
submission. Organizer/App Store Connect validation remains necessary.
|
||||
|
||||
The app embeds OpenSSL and libsrtp. Do not declare that encryption is limited
|
||||
to Apple system APIs or set `ITSAppUsesNonExemptEncryption=NO` without completing
|
||||
the applicable [export-compliance determination](https://developer.apple.com/help/app-store-connect/manage-app-information/overview-of-export-compliance).
|
||||
After that determination, supply the appropriate declaration and any required
|
||||
approval code in the distribution build. The repository deliberately does not
|
||||
preselect an exemption.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
CrossDesk 开源软件权利说明 / Open-source software rights
|
||||
|
||||
CrossDesk 按 GNU GPL 第 3 版发布,MiniRTC 按 GNU LGPL 第 3 版发布。
|
||||
其他组件适用各自列明的许可证。使用、复制、修改及再分发这些组件的权利,
|
||||
以相应许可证为准;本应用的普通使用条款不限制开源许可证授予你的权利。
|
||||
许可证中的保证、责任限制及其他条件仍然适用。
|
||||
|
||||
“本版本源码与构建说明”提供与发行版本对应的源码入口、子模块信息、
|
||||
依赖配方、补丁及构建说明。可以修改相关库后重新构建和链接应用。
|
||||
在自己的 iPhone 或 iPad 上运行修改版时,需要使用自己的 Apple 开发签名,
|
||||
并按构建文档设置开发团队和 Bundle Identifier。
|
||||
|
||||
CrossDesk is distributed under GNU GPL version 3; MiniRTC is distributed
|
||||
under GNU LGPL version 3. Other components are governed by their listed
|
||||
licenses. Your rights to use, copy, modify and redistribute those components
|
||||
are governed by the applicable licenses. The application's ordinary terms
|
||||
of use do not restrict rights granted by those open-source licenses.
|
||||
The licenses' warranty, liability and other provisions continue to apply.
|
||||
|
||||
The version-specific source and build links identify the application source,
|
||||
submodules, dependency recipes, patches and build instructions. You can
|
||||
modify the libraries and rebuild and relink the application. Running a
|
||||
modified build on your own iPhone or iPad requires your own Apple development
|
||||
signing setup, team and bundle identifier as described in the build guide.
|
||||
@@ -0,0 +1,906 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"platform": "iphoneos-arm64",
|
||||
"components": [
|
||||
{
|
||||
"id": "asio",
|
||||
"name": "Asio",
|
||||
"version": "1.32.0",
|
||||
"license": "BSL-1.0",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-32-0.tar.gz",
|
||||
"sha256": "f1b94b80eeb00bb63a3c8cef5047d4e409df4d8a3fe502305976965827d95672"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "asio/COPYING",
|
||||
"sha256": "5cc69ca9afd3417ed8e58bcd9c0bb730375e9e3e92b5aaf6483b93a2837e0cac"
|
||||
},
|
||||
{
|
||||
"path": "asio/LICENSE_1_0.txt",
|
||||
"sha256": "c9bff75738922193e67fa726fa225535870d2aa1059f91452c411736284ad566"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "52bddc12cf0837117e935ea5d71aa5c35e7fd6ae802aa7401c6dba598cae0989",
|
||||
"recipe_path": "xmake-repo/packages/a/asio/xmake.lua",
|
||||
"package": "asio",
|
||||
"notice_prefixes": [
|
||||
"asio/include/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "concurrentqueue",
|
||||
"name": "ConcurrentQueue",
|
||||
"version": "v1.0.4",
|
||||
"license": "(BSD-2-Clause OR BSL-1.0) / Zlib (semaphore)",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/cameron314/concurrentqueue/archive/refs/tags/v1.0.4.tar.gz",
|
||||
"sha256": "87fbc9884d60d0d4bf3462c18f4c0ee0a9311d0519341cac7cbd361c885e5281"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE.md",
|
||||
"sha256": "22331571e5e3dbe4c79ad715e23127bdf3beabd75052c953ddabf191ef799e14"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "23c69dc09488527b0d6adf1d25f795a9f0afdc7a1d594c44603c2dd07162faa4",
|
||||
"recipe_path": "xmake-repo/packages/c/concurrentqueue/xmake.lua",
|
||||
"package": "concurrentqueue",
|
||||
"notice_prefixes": [
|
||||
"concurrentqueue.h",
|
||||
"blockingconcurrentqueue.h",
|
||||
"lightweightsemaphore.h"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "crossdesk",
|
||||
"name": "CrossDesk / crossdesk_wire",
|
||||
"version": "application source",
|
||||
"license": "GPL-3.0-only",
|
||||
"source": {
|
||||
"kind": "repository",
|
||||
"url": "https://github.com/kunkundi/crossdesk",
|
||||
"revision": null
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE",
|
||||
"sha256": "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986"
|
||||
},
|
||||
{
|
||||
"path": "apps/ios/licenses/OPEN_SOURCE_NOTICE.txt",
|
||||
"sha256": "34720d126e6373603d0c1802a8c3276802b93f2a8fbbf2b61837c6fdefea8877"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dav1d",
|
||||
"name": "dav1d",
|
||||
"version": "1.4.3",
|
||||
"license": "BSD-2-Clause and patent grant",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://code.videolan.org/videolan/dav1d/-/archive/e9986de7f4264940af6fa1df1498fd2754077de4/dav1d-e9986de7f4264940af6fa1df1498fd2754077de4.tar.gz",
|
||||
"sha256": "ba147ff83d6c73f578a12f8eece636226e9c1f40cd55a11841a3bcb2c88cd46a",
|
||||
"revision": "e9986de7f4264940af6fa1df1498fd2754077de4"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "COPYING",
|
||||
"sha256": "b327887de263238deaa80c34cdd2ff3e0ba1d35db585ce14a37ce3e74ee389e9"
|
||||
},
|
||||
{
|
||||
"path": "doc/PATENTS",
|
||||
"sha256": "335eca574598bf4ca181b12f708d6669e5a5e78c8e1513e5b35fa1f03901484b"
|
||||
}
|
||||
],
|
||||
"package": "dav1d",
|
||||
"notice_prefixes": [
|
||||
"src/",
|
||||
"include/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "glib",
|
||||
"name": "GLib / GObject / GIO / GModule / GThread / GVDB",
|
||||
"version": "2.84.1",
|
||||
"license": "LGPL-2.1-or-later and embedded notices",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://download.gnome.org/sources/glib/2.84/glib-2.84.1.tar.xz",
|
||||
"sha256": "2b4bc2ec49611a5fc35f86aca855f2ed0196e69e53092bab6bb73396bf30789a"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "COPYING",
|
||||
"sha256": "fa6f36630bb1e0c571d34b2bbdf188d08495c9dbf58f28cac112f303fc1f58fb"
|
||||
},
|
||||
{
|
||||
"path": "LICENSES/Apache-2.0.txt",
|
||||
"sha256": "074e6e32c86a4c0ef8b3ed25b721ca23aca83df277cd88106ef7177c354615ff"
|
||||
},
|
||||
{
|
||||
"path": "LICENSES/CC0-1.0.txt",
|
||||
"sha256": "a2010f343487d3f7618affe54f789f5487602331c0a8d03f49e9a7c547cf0499"
|
||||
},
|
||||
{
|
||||
"path": "LICENSES/LGPL-2.1-only.txt",
|
||||
"sha256": "fa6f36630bb1e0c571d34b2bbdf188d08495c9dbf58f28cac112f303fc1f58fb"
|
||||
},
|
||||
{
|
||||
"path": "LICENSES/LLVM-exception.txt",
|
||||
"sha256": "e34c58338bd89d43e709e226610d8f32b3e3c47f4ad9a99a8dc1d4ac7842488e"
|
||||
},
|
||||
{
|
||||
"path": "LICENSES/MIT.txt",
|
||||
"sha256": "b85dcd3e453d05982552c52b5fc9e0bdd6d23c6f8e844b984a88af32570b0cc0"
|
||||
},
|
||||
{
|
||||
"path": "LICENSES/MPL-1.1.txt",
|
||||
"sha256": "6214f8b1300bb9f37b16ebc146f8f61af0187e0025042ff95c8b3030744a8795"
|
||||
},
|
||||
{
|
||||
"path": "subprojects/gvdb/COPYING",
|
||||
"sha256": "bdbcdaa7858872393e9d6b216bbdc8a23e99d98dd6cdb7cda19458025b9e19fc"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "773cac2e1a5aec087abc9347c7278f3fca40e36212b33a5f01012875cfbf7e6c",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/glib/xmake.lua",
|
||||
"package": "glib",
|
||||
"notice_prefixes": [
|
||||
"glib/",
|
||||
"gobject/",
|
||||
"gio/",
|
||||
"gmodule/",
|
||||
"subprojects/gvdb/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gssdp",
|
||||
"name": "GSSDP",
|
||||
"version": "1.6.4",
|
||||
"license": "LGPL-2.1-or-later",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://download.gnome.org/sources/gssdp/1.6/gssdp-1.6.4.tar.xz",
|
||||
"sha256": "ff97fdfb7f561d3e6813b4f6a2145259e7c2eff43cc0e63f3fd031d0b6266032"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "COPYING",
|
||||
"sha256": "dc626520dcd53a22f727af3ee42c770e56c97a64fe3adb063799d8ab032fe551"
|
||||
},
|
||||
{
|
||||
"path": "AUTHORS",
|
||||
"sha256": "66b5509fcdd21acaab5d7a3856e699bada9e9ebf0485312abbe987711bc7bd27"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "52667099a445f7004b9200df4ed771dd74e73745a5c6dc7fe728168e9f8b3e7a",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/gupnp/xmake.lua",
|
||||
"package": "gssdp",
|
||||
"notice_prefixes": [
|
||||
"libgssdp/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gupnp",
|
||||
"name": "GUPnP",
|
||||
"version": "1.6.9",
|
||||
"license": "LGPL-2.1-or-later",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://download.gnome.org/sources/gupnp/1.6/gupnp-1.6.9.tar.xz",
|
||||
"sha256": "2edb6ee3613558e62f538735368aee27151b7e09d4e2e2c51606833da801869b"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "COPYING",
|
||||
"sha256": "dc626520dcd53a22f727af3ee42c770e56c97a64fe3adb063799d8ab032fe551"
|
||||
},
|
||||
{
|
||||
"path": "AUTHORS",
|
||||
"sha256": "fadd2ab40439bc89d2e9540c02aef1f23edea96983af9cceaef345a9423d3c70"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "52667099a445f7004b9200df4ed771dd74e73745a5c6dc7fe728168e9f8b3e7a",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/gupnp/xmake.lua",
|
||||
"package": "gupnp",
|
||||
"notice_prefixes": [
|
||||
"libgupnp/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gupnp-igd",
|
||||
"name": "GUPnP IGD",
|
||||
"version": "1.6.0",
|
||||
"license": "LGPL-2.1-or-later",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://download.gnome.org/sources/gupnp-igd/1.6/gupnp-igd-1.6.0.tar.xz",
|
||||
"sha256": "4099978339ab22126d4968f2a332b6d094fc44c78797860781f1fc2f11771b74"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "COPYING",
|
||||
"sha256": "6095e9ffa777dd22839f7801aa845b31c9ed07f3d6bf8a26dc5d2dec8ccc0ef3"
|
||||
},
|
||||
{
|
||||
"path": "AUTHORS",
|
||||
"sha256": "77a943ea347578cfcffc032616707d43e5f5e03a12f864c0d421c3d632fc1582"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "52667099a445f7004b9200df4ed771dd74e73745a5c6dc7fe728168e9f8b3e7a",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/gupnp/xmake.lua",
|
||||
"package": "gupnp-igd",
|
||||
"notice_prefixes": [
|
||||
"libgupnp-igd/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "inih",
|
||||
"name": "inih / INIReader",
|
||||
"version": "6286ae6c1c015609d3070cc139e5feea900caf7c",
|
||||
"license": "BSD-3-Clause",
|
||||
"source": {
|
||||
"kind": "repository",
|
||||
"url": "https://github.com/kunkundi/minirtc",
|
||||
"revision": "6286ae6c1c015609d3070cc139e5feea900caf7c"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "deps/submodules/minirtc/src/inih/LICENSE",
|
||||
"sha256": "bd2a675a5a26fbff77f171f3a15afb1a9c7855be1a01c7e3fd16b8d2617acdda"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "nlohmann_json",
|
||||
"name": "JSON for Modern C++ / Hedley",
|
||||
"version": "v3.11.3",
|
||||
"license": "MIT",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/nlohmann/json/archive/refs/tags/v3.11.3.tar.gz",
|
||||
"sha256": "0d8ef5af7f9794e3263480193c491549b2ba6cc74bb018906202ada498a79406"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE.MIT",
|
||||
"sha256": "86b998c792894ccb911a1cb7994f7a9652894e7a094c0b5e45be2f553f45cf14"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "eadd211f0521a18207e9544c0c5a08c41dca578fce99df5c2577f6fcf2a0fdce",
|
||||
"recipe_path": "xmake-repo/packages/n/nlohmann_json/xmake.lua",
|
||||
"package": "nlohmann_json",
|
||||
"notice_prefixes": [
|
||||
"include/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "kcp",
|
||||
"name": "KCP",
|
||||
"version": "1.7",
|
||||
"license": "MIT",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/skywind3000/kcp/archive/refs/tags/1.7.tar.gz",
|
||||
"sha256": "b4d26994d95599ab0c44e1f93002f9fda275094a879d66c192d79d596529199e"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE",
|
||||
"sha256": "6587fd02a924b97dbd6758b0573ddec056fb494a84f3709c5aaa33ce1f7f029c"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "f86cac2c4b884f3347a39a59f3dd8de566c257c7fd481c6e49b05f4730f12c4f",
|
||||
"recipe_path": "xmake-repo/packages/k/kcp/xmake.lua",
|
||||
"package": "kcp",
|
||||
"notice_prefixes": [
|
||||
"ikcp.c",
|
||||
"ikcp.h"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "libdatachannel",
|
||||
"name": "libdatachannel",
|
||||
"version": "v0.24.5",
|
||||
"license": "MPL-2.0",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/paullouisageneau/libdatachannel/archive/refs/tags/v0.24.5.tar.gz",
|
||||
"sha256": "454537c3cd526bed935d847bb2dff4046f266eef84d43b2a5f2f2f293c0026f4"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE",
|
||||
"sha256": "fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "b3ec15652a47d65e0a1f85f62d02c098c5cf5b8d8c3cf7b03ff099efa7016326",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/libdatachannel/xmake.lua",
|
||||
"package": "libdatachannel",
|
||||
"notice_prefixes": [
|
||||
"include/",
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "libffi",
|
||||
"name": "libffi",
|
||||
"version": "3.4.8",
|
||||
"license": "MIT",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/libffi/libffi/releases/download/v3.4.8/libffi-3.4.8.tar.gz",
|
||||
"sha256": "bc9842a18898bfacb0ed1252c4febcc7e78fa139fd27fdc7a3e30d9d9356119b"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE",
|
||||
"sha256": "67894089811f93fca47a76f85e017da6f8582d4ba0905963c6e0f1ad6df7a195"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "de19ac4e3e91ef5715ac582c75e0d10404dc14ec4e33b2e764dc2637a78d24e9",
|
||||
"recipe_path": "xmake-repo/packages/l/libffi/xmake.lua",
|
||||
"package": "libffi",
|
||||
"notice_prefixes": [
|
||||
"src/",
|
||||
"include/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "libnice",
|
||||
"name": "libnice",
|
||||
"version": "0.1.24",
|
||||
"license": "MPL-1.1 OR LGPL-2.1-or-later",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://gitlab.freedesktop.org/libnice/libnice/-/archive/0.1.24/libnice-0.1.24.tar.gz",
|
||||
"sha256": "1da5ac13ed5d4e175e0d2d46ad3748c6635244f8f3eb2b8e31578ef59aa2ddce"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "COPYING",
|
||||
"sha256": "e5504e5ffe894870ea1a652df957a120ead9c3f9a5e9d314ca41ba06f2d72024"
|
||||
},
|
||||
{
|
||||
"path": "COPYING.LGPL",
|
||||
"sha256": "a9bdde5616ecdd1e980b44f360600ee8783b1f99b8cc83a2beb163a0a390e861"
|
||||
},
|
||||
{
|
||||
"path": "COPYING.MPL",
|
||||
"sha256": "3b3c75ae6c663c4f93987647eb5463d33dc1602a1aded3ac479773f2c3928e3a"
|
||||
},
|
||||
{
|
||||
"path": "AUTHORS",
|
||||
"sha256": "d72eb6fa9d5e93abb11d13b6c6f04fda2ba183d0705e703887cda243f6f1eead"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "7d7c30d7d63245d0190e64b050184f6ff6933ef9133bff5329d62a7b9b685441",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/libnice/xmake.lua",
|
||||
"package": "libnice",
|
||||
"notice_prefixes": [
|
||||
"agent/",
|
||||
"nice/",
|
||||
"random/",
|
||||
"socket/",
|
||||
"stun/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "upnp-libpsl",
|
||||
"name": "libpsl",
|
||||
"version": "0.21.5",
|
||||
"license": "MIT / BSD-3-Clause",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/rockdaboot/libpsl/releases/download/0.21.5/libpsl-0.21.5.tar.gz",
|
||||
"sha256": "1dcc9ceae8b128f3c0b3f654decd0e1e891afc6ff81098f227ef260449dae208"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE",
|
||||
"sha256": "322392e6001b2b3b0fca705dc9f11f26e12441188ecde50eb1d22909980b2925"
|
||||
},
|
||||
{
|
||||
"path": "src/LICENSE.chromium",
|
||||
"sha256": "1d7f52747a9169751cdf2641a8299c0098e9ecdf9429296ffd55bdb14c9ed5b3"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "52667099a445f7004b9200df4ed771dd74e73745a5c6dc7fe728168e9f8b3e7a",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/gupnp/xmake.lua",
|
||||
"package": "upnp-libpsl",
|
||||
"notice_prefixes": [
|
||||
"src/",
|
||||
"include/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "libsoup",
|
||||
"name": "libsoup",
|
||||
"version": "3.6.5",
|
||||
"license": "LGPL-2.0-or-later",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://download.gnome.org/sources/libsoup/3.6/libsoup-3.6.5.tar.xz",
|
||||
"sha256": "6891765aac3e949017945c3eaebd8cc8216df772456dc9f460976fbdb7ada234"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "COPYING",
|
||||
"sha256": "b7993225104d90ddd8024fd838faf300bea5e83d91203eab98e29512acebd69c"
|
||||
},
|
||||
{
|
||||
"path": "AUTHORS",
|
||||
"sha256": "f8776285b4556368878a1b903a1f0e6d6cee342284961562a7716a2dacc28321"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "52667099a445f7004b9200df4ed771dd74e73745a5c6dc7fe728168e9f8b3e7a",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/gupnp/xmake.lua",
|
||||
"package": "libsoup",
|
||||
"notice_prefixes": [
|
||||
"libsoup/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "libsrtp",
|
||||
"name": "libsrtp",
|
||||
"version": "v2.7.0",
|
||||
"license": "BSD-3-Clause",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/cisco/libsrtp/archive/refs/tags/v2.7.0.tar.gz",
|
||||
"sha256": "54facb1727a557c2a76b91194dcb2d0a453aaf8e2d0cbbf1e3c2848c323e28ad"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE",
|
||||
"sha256": "8e19d42a1eec9561f3f347253ddf2e385c55f392f025bb0fd41b88dbf38db5ae"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "9629edb8d395ea3017af1702cafea57173ca0a6a1ea305a6174b1936598faec6",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/libsrtp/xmake.lua",
|
||||
"package": "libsrtp",
|
||||
"notice_prefixes": [
|
||||
"srtp/",
|
||||
"crypto/",
|
||||
"include/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "upnp-libxml2",
|
||||
"name": "libxml2",
|
||||
"version": "2.14.6",
|
||||
"license": "MIT and embedded notices",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://download.gnome.org/sources/libxml2/2.14/libxml2-2.14.6.tar.xz",
|
||||
"sha256": "7ce458a0affeb83f0b55f1f4f9e0e55735dbfc1a9de124ee86fb4a66b597203a"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "Copyright",
|
||||
"sha256": "5d4873884a890122a4b9b20ad56ac6f7da1d796a5bfcf04a427970ac96217626"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "52667099a445f7004b9200df4ed771dd74e73745a5c6dc7fe728168e9f8b3e7a",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/gupnp/xmake.lua",
|
||||
"package": "upnp-libxml2",
|
||||
"notice_prefixes": [
|
||||
"include/"
|
||||
],
|
||||
"notice_root_files": true
|
||||
},
|
||||
{
|
||||
"id": "libyuv",
|
||||
"name": "libyuv",
|
||||
"version": "2025.8.14",
|
||||
"license": "BSD-3-Clause and patent grant",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://codeload.github.com/kunkundi/libyuv/tar.gz/ec10b61c58ee4b8fbe648d2744d9dad9ccba6430",
|
||||
"sha256": "e6e83acefcb9322a498a04c2c417491ceab18cf9750d306110ab5ab27d84377b",
|
||||
"revision": "ec10b61c58ee4b8fbe648d2744d9dad9ccba6430"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE",
|
||||
"sha256": "2b2cc1180c7e6988328ad2033b04b80117419db9c4c584918bbb3cfec7e9364f"
|
||||
},
|
||||
{
|
||||
"path": "PATENTS",
|
||||
"sha256": "4da84211f947c6f9f7dd8fbce513e1522e01833407ae732b8c9a2bbb75fed495"
|
||||
},
|
||||
{
|
||||
"path": "AUTHORS",
|
||||
"sha256": "0c4a51c72a06b3431bb2b750e3b04dbe3694b22e2fcae6e370216424eec56d52"
|
||||
}
|
||||
],
|
||||
"package": "libyuv",
|
||||
"notice_prefixes": [
|
||||
"source/",
|
||||
"include/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "minirtc",
|
||||
"name": "MiniRTC",
|
||||
"version": "6286ae6c1c015609d3070cc139e5feea900caf7c",
|
||||
"license": "LGPL-3.0-only",
|
||||
"source": {
|
||||
"kind": "repository",
|
||||
"url": "https://github.com/kunkundi/minirtc",
|
||||
"revision": "6286ae6c1c015609d3070cc139e5feea900caf7c"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "deps/submodules/minirtc/LICENSE",
|
||||
"sha256": "ea8af5e789cb2d4e9b10bce3874982ade163b749b6bfbdb32e2df21c4d106de1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "nghttp2",
|
||||
"name": "nghttp2",
|
||||
"version": "1.68.0",
|
||||
"license": "MIT",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/nghttp2/nghttp2/releases/download/v1.68.0/nghttp2-1.68.0.tar.gz",
|
||||
"sha256": "2c16ffc588ad3f9e2613c3fad72db48ecb5ce15bc362fcc85b342e48daf51013"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "COPYING",
|
||||
"sha256": "6b94f3abc1aabd0c72a7c7d92a77f79dda7c8a0cb3df839a97890b4116a2de2a"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "eec10214a906c663f74e3e6bf8952cddec39fec3cac710c10d8b9ea669c9ab79",
|
||||
"recipe_path": "xmake-repo/packages/n/nghttp2/xmake.lua",
|
||||
"package": "nghttp2",
|
||||
"notice_prefixes": [
|
||||
"lib/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "openfec",
|
||||
"name": "OpenFEC",
|
||||
"version": "1.4.2",
|
||||
"license": "CeCILL-C 1.0 / BSD / CC-BY-SA-3.0",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/kunkundi/openfec/archive/refs/tags/1.4.2.tar.gz",
|
||||
"sha256": "5aa19dc56038f4b19927efb53ca28e896fb05f068a4715de512f0709420f03be"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENCE_CeCILL-C_V1-en.txt",
|
||||
"sha256": "b48f5dc16f144cd79ddaadce3cc8ef183778a0b4f75ab4362e134b347ca20f3d",
|
||||
"encoding": "iso-8859-1"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "8644c6306ad6ba4c974dd949d56178c761b72d503fe7c4a577a23817effa6243",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/openfec/xmake.lua",
|
||||
"package": "openfec",
|
||||
"notice_prefixes": [
|
||||
"src/lib_common/",
|
||||
"src/lib_stable/"
|
||||
],
|
||||
"notice_encodings": {
|
||||
"src/lib_common/linear_binary_codes_utils/binary_matrix/of_hamming_weight.c": "gb18030",
|
||||
"src/lib_common/linear_binary_codes_utils/binary_matrix/of_hamming_weight.h": "gb18030"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "openssl3",
|
||||
"name": "OpenSSL",
|
||||
"version": "3.3.2",
|
||||
"license": "Apache-2.0 and embedded notices",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/openssl/openssl/archive/refs/tags/openssl-3.3.2.zip",
|
||||
"sha256": "4cda357946f9dd5541b565dba35348d614288e88aeb499045018970c789c9d61"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE.txt",
|
||||
"sha256": "7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "864c308edf59fd802babd45d064d8575635ecd577e829df2b2c7ac7a4d06d4d7",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/openssl/xmake.lua",
|
||||
"package": "openssl3",
|
||||
"notice_prefixes": [
|
||||
"crypto/",
|
||||
"ssl/",
|
||||
"include/",
|
||||
"providers/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "libopus",
|
||||
"name": "Opus",
|
||||
"version": "1.5.1",
|
||||
"license": "BSD-3-Clause and patent grants",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://downloads.xiph.org/releases/opus/opus-1.5.1.tar.gz",
|
||||
"sha256": "b84610959b8d417b611aa12a22565e0a3732097c6389d19098d844543e340f85"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "COPYING",
|
||||
"sha256": "01e1167d54a096d123cf6dfbbeb19587278845c6481d2d66d545669846079551"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "c965643eb4e093858c27c402e6cde544620460f2169c89d8ff2bab01111d022b",
|
||||
"recipe_path": "xmake-repo/packages/l/libopus/xmake.lua",
|
||||
"package": "libopus",
|
||||
"notice_prefixes": [
|
||||
"src/",
|
||||
"include/",
|
||||
"celt/",
|
||||
"silk/",
|
||||
"dnn/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pcre2",
|
||||
"name": "PCRE2 / SLJIT",
|
||||
"version": "10.47",
|
||||
"license": "BSD-3-Clause / BSD-2-Clause",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.47/pcre2-10.47.tar.gz",
|
||||
"sha256": "c08ae2388ef333e8403e670ad70c0a11f1eed021fd88308d7e02f596fcd9dc16"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENCE.md",
|
||||
"sha256": "197d8a73ffee0d6b09adba2f9c677b5f5aede24edf89258a68e48248d010d811"
|
||||
},
|
||||
{
|
||||
"path": "COPYING",
|
||||
"sha256": "99272c55f3dcfa07a8a7e15a5c1a33096e4727de74241d65fa049fccfdd59507"
|
||||
},
|
||||
{
|
||||
"path": "deps/sljit/LICENSE",
|
||||
"sha256": "5f216505c0f6ea3273caec89e766eef93cdeb7bbb0c429f9360116d7c938feeb"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "821f1fbc91434f4324be88eeea754db09c5d7be31446242764861ca2aee136b7",
|
||||
"recipe_path": "xmake-repo/packages/p/pcre2/xmake.lua",
|
||||
"package": "pcre2",
|
||||
"notice_prefixes": [
|
||||
"src/",
|
||||
"deps/sljit/sljitLir.c",
|
||||
"deps/sljit/sljitLir.h"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "plog",
|
||||
"name": "plog",
|
||||
"version": "1.1.11",
|
||||
"license": "MIT",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/SergiusTheBest/plog/archive/refs/tags/1.1.11.tar.gz",
|
||||
"sha256": "d60b8b35f56c7c852b7f00f58cbe9c1c2e9e59566c5b200512d0cdbb6309a7c2"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE",
|
||||
"sha256": "e4d01796524cbc13b1571a1f0914823c6a23b316ba039e7014f47a4eef7fd4c3"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "deb525d09b5b79351eeb524ab3bc6a3009e434974e585e5e8852c491d7fa07c8",
|
||||
"recipe_path": "xmake-repo/packages/p/plog/xmake.lua",
|
||||
"package": "plog",
|
||||
"notice_prefixes": [
|
||||
"include/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "proxy-libintl",
|
||||
"name": "proxy-libintl (GLib libintl)",
|
||||
"version": "33934de09af6a6627eb44e310a8079df009abdbb",
|
||||
"license": "LGPL-2.0-or-later",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://codeload.github.com/frida/proxy-libintl/tar.gz/33934de09af6a6627eb44e310a8079df009abdbb",
|
||||
"sha256": "4ab5af85ec6124cb526a15c58bbc0877d241807e30855779808dbf9292d861aa",
|
||||
"revision": "33934de09af6a6627eb44e310a8079df009abdbb"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "COPYING",
|
||||
"sha256": "d245807f90032872d1438d741ed21e2490e1175dc8aa3afa5ddb6c8e529b58e5"
|
||||
}
|
||||
],
|
||||
"bundled_by": "glib",
|
||||
"notice_prefixes": [
|
||||
"src/",
|
||||
"libintl.c",
|
||||
"libintl.h"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "spdlog",
|
||||
"name": "spdlog / fmt 10.2.1",
|
||||
"version": "v1.14.1",
|
||||
"license": "MIT",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/gabime/spdlog/archive/refs/tags/v1.14.1.zip",
|
||||
"sha256": "429dfdf3afc1984feb59e414353c21c110bc79609f6d7899d52f6aa388646f6d"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE",
|
||||
"sha256": "4ccecab18d1ff0b61174fe3d6c430541625d3ddb865b0d5887db296f883c76e7"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "5b299effb366420b4920c5b89e6535b76173136d91ee01f8f7d3423486a93549",
|
||||
"recipe_path": "xmake-repo/packages/s/spdlog/xmake.lua",
|
||||
"package": "spdlog",
|
||||
"notice_prefixes": [
|
||||
"include/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sqlite3",
|
||||
"name": "SQLite",
|
||||
"version": "3.50.0+400",
|
||||
"license": "Public domain",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://sqlite.org/2025/sqlite-autoconf-3500400.tar.gz",
|
||||
"sha256": "a3db587a1b92ee5ddac2f66b3edb41b26f9c867275782d46c3a088977d6a5b18"
|
||||
},
|
||||
"documents": [],
|
||||
"recipe_sha256": "a752522259dbd722ea79beaa01e5287429ccb37643ccffbecf5fa30ec4df30fd",
|
||||
"recipe_path": "xmake-repo/packages/s/sqlite3/xmake.lua",
|
||||
"package": "sqlite3",
|
||||
"notice_prefixes": [
|
||||
"sqlite3.h"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "svt-av1",
|
||||
"name": "SVT-AV1",
|
||||
"version": "v3.0.2",
|
||||
"license": "BSD-3-Clause-Clear / BSD-2-Clause, AOMedia patent license and embedded notices",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://gitlab.com/AOMediaCodec/SVT-AV1/-/archive/efc905a7c2ed155b3654d7968173622734eeb0c0/SVT-AV1-efc905a7c2ed155b3654d7968173622734eeb0c0.tar.gz",
|
||||
"sha256": "50a897d58470b06f99b386dc691ab63e802962421e6abdb75e4d234a3e73cd6a",
|
||||
"revision": "efc905a7c2ed155b3654d7968173622734eeb0c0"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE.md",
|
||||
"sha256": "0acc2fcb27472bdc9aaf8b71f37055bbdac4f54671b7d922f241bd7fcd0dd3e6"
|
||||
},
|
||||
{
|
||||
"path": "LICENSE-BSD2.md",
|
||||
"sha256": "c582fcbeb9cf972a60d298bfaf41dc5adff2dd14208946edcb9bb56a281e8438"
|
||||
},
|
||||
{
|
||||
"path": "PATENTS.md",
|
||||
"sha256": "20678ab10402659106dc4c147c97b2a6e94b5c0695415e15a8f195ebc3547922"
|
||||
},
|
||||
{
|
||||
"path": "third_party/fastfeat/LICENSE",
|
||||
"sha256": "043dcfd059386f9facd376351b2bd79325778744aa442177390cdfcca54babed"
|
||||
},
|
||||
{
|
||||
"path": "third_party/safestringlib/LICENSE",
|
||||
"sha256": "c0f99a208799ca20c9ccf31a30533d49334eb458dca5a3d14f9afe3e884e5d7d"
|
||||
}
|
||||
],
|
||||
"package": "svt-av1",
|
||||
"notice_prefixes": [
|
||||
"Source/",
|
||||
"third_party/fastfeat/",
|
||||
"third_party/safestringlib/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "usrsctp",
|
||||
"name": "usrsctp",
|
||||
"version": "0.9.5+0",
|
||||
"license": "BSD-3-Clause and embedded notices",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/sctplab/usrsctp/archive/refs/tags/0.9.5.0.tar.gz",
|
||||
"sha256": "260107caf318650a57a8caa593550e39bca6943e93f970c80d6c17e59d62cd92"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE.md",
|
||||
"sha256": "fa53711b25af4b9a9b8dadfea3cb38166ec4b96760c8d62b284055554537d9ef"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "e3dac7d1db8e5bc79d99851e2486371af19962a4b8fca6eaac3156c00eb73b92",
|
||||
"recipe_path": "xmake-repo/packages/u/usrsctp/xmake.lua",
|
||||
"package": "usrsctp",
|
||||
"notice_prefixes": [
|
||||
"usrsctplib/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "webrtc",
|
||||
"name": "WebRTC-derived code in MiniRTC",
|
||||
"version": "6286ae6c1c015609d3070cc139e5feea900caf7c",
|
||||
"license": "BSD-3-Clause and patent grant",
|
||||
"source": {
|
||||
"kind": "repository",
|
||||
"url": "https://github.com/kunkundi/minirtc",
|
||||
"revision": "6286ae6c1c015609d3070cc139e5feea900caf7c"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "deps/submodules/minirtc/thirdparty/webrtc/LICENSE",
|
||||
"sha256": "ab00a482b6a3902e40211b43c5d0441962ea99b6cc7c25c0f243fa270b78d482"
|
||||
},
|
||||
{
|
||||
"path": "deps/submodules/minirtc/thirdparty/webrtc/PATENTS",
|
||||
"sha256": "01462e2068d1a04c2274f3389773014c14ed9bc3446b28303543bd3e3c064145"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "websocketpp",
|
||||
"name": "WebSocket++",
|
||||
"version": "0.8.2",
|
||||
"license": "BSD-3-Clause and embedded notices",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/zaphoyd/websocketpp/archive/refs/tags/0.8.2.tar.gz",
|
||||
"sha256": "6ce889d85ecdc2d8fa07408d6787e7352510750daa66b5ad44aacb47bea76755"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "COPYING",
|
||||
"sha256": "b31e597bc777d884a39663c11d0557a0af40780e1f451de11ea5103924a69667"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "beac245a717c2bc3bc8d17265d9fc9b54f699f3833b11513f8723c4dbaffd539",
|
||||
"recipe_path": "deps/submodules/minirtc/thirdparty/websocketpp/xmake.lua",
|
||||
"package": "websocketpp",
|
||||
"notice_prefixes": [
|
||||
"websocketpp/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "zlib",
|
||||
"name": "zlib",
|
||||
"version": "v1.3.2",
|
||||
"license": "Zlib",
|
||||
"source": {
|
||||
"kind": "archive",
|
||||
"url": "https://github.com/madler/zlib/archive/refs/tags/v1.3.2.tar.gz",
|
||||
"sha256": "b99a0b86c0ba9360ec7e78c4f1e43b1cbdf1e6936c8fa0f6835c0cd694a495a1"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"path": "LICENSE",
|
||||
"sha256": "e32ff4e00d9d94930537635291da39e7e612703334bf6fde8c7f1686fe8a45a2"
|
||||
}
|
||||
],
|
||||
"recipe_sha256": "7832bcb086709a0739ae1abeff035f7a103c7169dc8af61ebf91dd887fcbd054",
|
||||
"recipe_path": "xmake-repo/packages/z/zlib/xmake.lua",
|
||||
"package": "zlib",
|
||||
"notice_prefixes": [
|
||||
"zlib.h"
|
||||
]
|
||||
}
|
||||
],
|
||||
"bundle_sha256": "09376251bbda3f5a1f7112f15dcbb1259fef7898f85b45aacafa767348aafdc2",
|
||||
"toolchain": {
|
||||
"xmake_version": "3.1.1",
|
||||
"xmake_repository_revision": "121109352da272cd1a3d4ed52f2dcda34f839955"
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,10 @@ fi
|
||||
XMAKE_DEVELOPER_DIR="${DEVELOPER_DIR:-$(xcode-select -p)}"
|
||||
XMAKE_TOOLCHAIN_BIN="${XMAKE_DEVELOPER_DIR}/Toolchains/XcodeDefault.xctoolchain/usr/bin"
|
||||
run_xmake() {
|
||||
local xmake_env=()
|
||||
if [[ -n "${XMAKE_GLOBALDIR:-}" ]]; then
|
||||
xmake_env+=("XMAKE_GLOBALDIR=${XMAKE_GLOBALDIR}")
|
||||
fi
|
||||
/usr/bin/env -i \
|
||||
HOME="${HOME}" \
|
||||
PATH="${XMAKE_TOOLCHAIN_BIN}:${PATH}" \
|
||||
@@ -69,11 +73,16 @@ run_xmake() {
|
||||
DEVELOPER_DIR="${XMAKE_DEVELOPER_DIR}" \
|
||||
XMAKE_PKG_INSTALLDIR="${XMAKE_PKG_INSTALLDIR}" \
|
||||
CROSSDESK_SOURCE_DIR="${REPO_DIR}" \
|
||||
"${xmake_env[@]}" \
|
||||
"${XMAKE_BIN}" "$@"
|
||||
}
|
||||
|
||||
OUTPUT_DIR="${IOS_DIR}/Vendor/iphoneos/${CONFIG_NAME}"
|
||||
OUTPUT_LIBRARY="${OUTPUT_DIR}/libCrossDeskMiniRTC.a"
|
||||
XMAKE_REPOSITORY_DIR="$(run_xmake lua -c 'import("core.base.global"); print(path.join(global.directory(), "repositories", "xmake-repo"))')"
|
||||
mkdir -p "${OUTPUT_DIR}"
|
||||
print -r -- "${XMAKE_REPOSITORY_DIR}" > "${OUTPUT_DIR}/xmake-repository.txt"
|
||||
run_xmake --version > "${OUTPUT_DIR}/xmake-version.txt"
|
||||
MINIRTC_BUILD_DIR="${IOS_DIR}/.xmake/minirtc-build"
|
||||
MINIRTC_LIBRARY="${MINIRTC_BUILD_DIR}/iphoneos/arm64/${MODE}/libminirtc.a"
|
||||
WIRE_BUILD_DIR="${IOS_DIR}/.xmake/wire-build"
|
||||
@@ -106,7 +115,8 @@ configure_xmake() {
|
||||
clean_flags=(-c)
|
||||
fi
|
||||
run_xmake f -P "${project_dir}" "${clean_flags[@]}" -o "${build_dir}" \
|
||||
-p iphoneos -a arm64 -m "${MODE}" --target_minver=16.0 -y "$@"
|
||||
-p iphoneos -a arm64 -m "${MODE}" --target_minver=16.0 \
|
||||
--policies=package.precompiled:n -y "$@"
|
||||
mkdir -p "${build_dir}"
|
||||
print -r -- "${CONFIG_SIGNATURE}" > "${stamp}"
|
||||
}
|
||||
@@ -148,6 +158,8 @@ TARGET_INFO="$(cd "${MINIRTC_DIR}" && TERM=dumb NO_COLOR=1 \
|
||||
CLEAN_INFO="$(print -r -- "${TARGET_INFO}" | sed $'s/\033\\[[0-9;]*[[:alpha:]]//g')"
|
||||
LINK_DIRS=("${(@f)$(print -r -- "${CLEAN_INFO}" | sed -nE 's|.*-> (/.*)/lib -> package.*|\1/lib|p' | sort -u)}")
|
||||
|
||||
print -r -- "${CLEAN_INFO}" > "${OUTPUT_DIR}/minirtc-target-info.txt"
|
||||
|
||||
REQUIRED_LINKS=(
|
||||
nice glib-2.0 gobject-2.0 gmodule-2.0 gio-2.0 gthread-2.0 intl
|
||||
gupnp-igd-1.6 gupnp-1.6 gssdp-1.6 soup-3.0 nghttp2 sqlite3 psl xml2
|
||||
@@ -158,6 +170,7 @@ if [[ "${MINIRTC_ENABLE_AOM}" == "true" ]]; then
|
||||
REQUIRED_LINKS+=(aom)
|
||||
fi
|
||||
DEPENDENCY_ARCHIVES=()
|
||||
MERGE_ARCHIVES=()
|
||||
|
||||
for link_name in "${REQUIRED_LINKS[@]}"; do
|
||||
archive_path=""
|
||||
@@ -173,6 +186,11 @@ for link_name in "${REQUIRED_LINKS[@]}"; do
|
||||
exit 66
|
||||
fi
|
||||
DEPENDENCY_ARCHIVES+=("${archive_path}")
|
||||
# OpenSSL is linked separately as a static framework with its SDK privacy
|
||||
# resources. Keep both archives in the input manifest for source validation.
|
||||
if [[ "${link_name}" != "ssl" && "${link_name}" != "crypto" ]]; then
|
||||
MERGE_ARCHIVES+=("${archive_path}")
|
||||
fi
|
||||
done
|
||||
|
||||
TEMP_LIBRARY="${OUTPUT_LIBRARY}.tmp"
|
||||
@@ -204,7 +222,7 @@ if [[ -f "${OUTPUT_LIBRARY}" ]] && cmp -s "${TEMP_MANIFEST}" "${MERGE_MANIFEST}"
|
||||
fi
|
||||
|
||||
/usr/bin/libtool -static -o "${TEMP_LIBRARY}" \
|
||||
"${MINIRTC_LIBRARY}" "${DEPENDENCY_ARCHIVES[@]}" \
|
||||
"${MINIRTC_LIBRARY}" "${MERGE_ARCHIVES[@]}" \
|
||||
"${WIRE_LIBRARY}"
|
||||
verify_no_openh264 "${TEMP_LIBRARY}"
|
||||
mv -f "${TEMP_LIBRARY}" "${OUTPUT_LIBRARY}"
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Collect iOS notices from pinned source archives; validate them offline at build time."""
|
||||
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
IOS = Path(__file__).resolve().parents[1]
|
||||
REPO = IOS.parents[1]
|
||||
LOCK = IOS / "licenses/sources.json"
|
||||
OUTPUT = IOS / "CrossDeskMobile/Resources/ThirdPartyLicenses.json"
|
||||
COMMENT = re.compile(rb"/\*.*?\*/|(?://[^\n]*(?:\n|$))+", re.S)
|
||||
RIGHTS = re.compile(rb"copyright|SPDX-License|permission is hereby|author disclaims|public domain", re.I)
|
||||
|
||||
|
||||
def digest(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def json_bytes(value):
|
||||
return (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode()
|
||||
|
||||
|
||||
def checked(data, expected, label):
|
||||
if digest(data) != expected:
|
||||
raise ValueError(f"Source checksum mismatch: {label}")
|
||||
return data
|
||||
|
||||
|
||||
def archive_path(component, cache):
|
||||
source = component["source"]
|
||||
target = cache / source["sha256"]
|
||||
if target.exists():
|
||||
checked(target.read_bytes(), source["sha256"], component["id"])
|
||||
return target
|
||||
cache.mkdir(parents=True, exist_ok=True)
|
||||
# Xmake may remove extracted sources or COPYING; read the original archive.
|
||||
candidates = (Path.home() / ".xmake/cache/packages").glob(
|
||||
f"*/{component['id'][0]}/{component['id']}/{component['version']}/*")
|
||||
for candidate in candidates:
|
||||
if candidate.is_file() and candidate.name.endswith((".tar.gz", ".tar.xz", ".zip")):
|
||||
data = candidate.read_bytes()
|
||||
if digest(data) == source["sha256"]:
|
||||
target.write_bytes(data)
|
||||
return target
|
||||
request = urllib.request.Request(source["url"], headers={"User-Agent": "CrossDesk-license-collector"})
|
||||
with urllib.request.urlopen(request, timeout=90) as response:
|
||||
data = checked(response.read(), source["sha256"], component["id"])
|
||||
target.write_bytes(data)
|
||||
return target
|
||||
|
||||
|
||||
@contextmanager
|
||||
def source_files(component, cache):
|
||||
if component["source"]["kind"] == "repository":
|
||||
yield {}, lambda name: (REPO / name).read_bytes()
|
||||
return
|
||||
path = archive_path(component, cache)
|
||||
if zipfile.is_zipfile(path):
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
names = {name.split("/", 1)[1]: name for name in archive.namelist()
|
||||
if "/" in name and not name.endswith("/")}
|
||||
yield names, lambda name: archive.read(names[name])
|
||||
else:
|
||||
with tarfile.open(path) as archive:
|
||||
names = {member.name.split("/", 1)[1]: member.name for member in archive
|
||||
if "/" in member.name and not member.isdir()}
|
||||
# No filesystem extraction. tarfile resolves COPYING symlinks within
|
||||
# the verified archive, including GLib's LICENSES directory.
|
||||
yield names, lambda name: archive.extractfile(names[name]).read()
|
||||
|
||||
|
||||
def component_documents(component, names, read):
|
||||
documents = []
|
||||
for item in component["documents"]:
|
||||
data = checked(read(item["path"]), item["sha256"], item["path"])
|
||||
text = data.decode(item.get("encoding", "utf-8"))
|
||||
if "end_before" in item:
|
||||
if item["end_before"] not in text:
|
||||
raise ValueError(f"Missing notice boundary: {item['path']}")
|
||||
text = text.split(item["end_before"], 1)[0]
|
||||
documents.append({"title": item["path"], "text": text})
|
||||
notices = defaultdict(list)
|
||||
for name in sorted(names):
|
||||
path = Path(name)
|
||||
selected = any(name.startswith(prefix) for prefix in component.get("notice_prefixes", []))
|
||||
selected |= component.get("notice_root_files", False) and "/" not in name
|
||||
if not selected or path.suffix not in (".h", ".hpp", ".c", ".cc", ".cpp", ".S", ".s", ".inc"):
|
||||
continue
|
||||
if any(part.lower() in ("test", "tests", "benchmarks", "examples") for part in path.parts):
|
||||
continue
|
||||
for block in COMMENT.findall(read(name)[:16384]):
|
||||
if RIGHTS.search(block):
|
||||
# Preserve complete original comment blocks, including bundled
|
||||
# fmt, semaphore and per-file copyright/permission exceptions.
|
||||
encoding = component.get("notice_encodings", {}).get(name, "utf-8")
|
||||
notices[block.decode(encoding)].append(name)
|
||||
if notices:
|
||||
documents.append({"title": "Source copyright and license notices", "text": "\n\n".join(
|
||||
"Files: " + ", ".join(files) + "\n\n" + text for text, files in notices.items())})
|
||||
if not documents:
|
||||
raise ValueError(f"No notices collected for {component['id']}")
|
||||
return documents
|
||||
|
||||
|
||||
def generate(lock, cache):
|
||||
components = []
|
||||
for component in lock["components"]:
|
||||
with source_files(component, cache) as (names, read):
|
||||
documents = component_documents(component, names, read)
|
||||
source = component["source"]
|
||||
source_url = source["url"]
|
||||
if source["kind"] == "repository" and source.get("revision"):
|
||||
source_url += "/tree/" + source["revision"]
|
||||
entry = {key: component[key] for key in ("id", "name", "version", "license")} | {
|
||||
"sourceURL": source_url, "documents": documents}
|
||||
recipe = component.get("recipe_path", "")
|
||||
if recipe.startswith("deps/submodules/minirtc/"):
|
||||
mini = next(c for c in lock["components"] if c["id"] == "minirtc")
|
||||
entry["buildSourceURL"] = (mini["source"]["url"] + "/tree/" +
|
||||
mini["source"]["revision"] + "/" +
|
||||
str(Path(recipe).relative_to("deps/submodules/minirtc").parent))
|
||||
components.append(entry)
|
||||
print(f"Collected {component['name']} {component['version']}")
|
||||
return json_bytes({"schemaVersion": 1, "components": components})
|
||||
|
||||
|
||||
def verify(lock, inputs, target_info, xmake_repository=None, xmake_version=None):
|
||||
checked(OUTPUT.read_bytes(), lock["bundle_sha256"], str(OUTPUT))
|
||||
if xmake_version is not None:
|
||||
version = re.search(r"xmake v(\d+\.\d+\.\d+)", xmake_version.read_text())
|
||||
if not version or version[1] != lock["toolchain"]["xmake_version"]:
|
||||
raise ValueError("Xmake version differs from the reviewed iOS toolchain; see apps/ios/README.md")
|
||||
if xmake_repository is not None:
|
||||
revision = subprocess.check_output(
|
||||
["git", "-C", str(xmake_repository), "rev-parse", "HEAD"], text=True).strip()
|
||||
if revision != lock["toolchain"]["xmake_repository_revision"]:
|
||||
raise ValueError("Xmake package repository differs from the reviewed iOS source catalog")
|
||||
changes = subprocess.check_output(
|
||||
["git", "-C", str(xmake_repository), "status", "--porcelain", "--untracked-files=normal"],
|
||||
text=True).strip()
|
||||
if changes:
|
||||
raise ValueError("Xmake package repository has local changes; use the unmodified pinned revision")
|
||||
for component in lock["components"]:
|
||||
recipe = component.get("recipe_path", "")
|
||||
if recipe.startswith("deps/"):
|
||||
checked((REPO / recipe).read_bytes(), component["recipe_sha256"], recipe)
|
||||
elif recipe.startswith("xmake-repo/") and xmake_repository is not None:
|
||||
checked((xmake_repository / Path(recipe).relative_to("xmake-repo")).read_bytes(),
|
||||
component["recipe_sha256"], recipe)
|
||||
if component["source"]["kind"] == "repository":
|
||||
for item in component["documents"]:
|
||||
checked((REPO / item["path"]).read_bytes(), item["sha256"], item["path"])
|
||||
if inputs is None:
|
||||
return
|
||||
if target_info is None:
|
||||
raise ValueError("--target-info is required with --inputs (includes header-only dependencies)")
|
||||
known = {c["package"]: c["version"] for c in lock["components"] if "package" in c}
|
||||
seen = {}
|
||||
# Package names/versions come from resolved Xmake link and include paths,
|
||||
# never from an inventory of all packages left in the installation cache.
|
||||
pattern = r"/packages/[^/\s]+/([^/\s]+)/([^/\s]+)/[a-f0-9]+/"
|
||||
for name, version in re.findall(pattern, inputs.read_text() + target_info.read_text()):
|
||||
if name not in known or known[name] != version:
|
||||
raise ValueError(f"Unreviewed iOS dependency: {name} {version}; regenerate licenses from its source")
|
||||
seen[name] = version
|
||||
if set(seen) != set(known):
|
||||
raise ValueError(f"License/build dependency mismatch; missing: {sorted(set(known) - set(seen))}")
|
||||
for line in inputs.read_text().splitlines():
|
||||
sha, path = line.split(maxsplit=1)
|
||||
checked(Path(path).read_bytes(), sha, path)
|
||||
revision = subprocess.check_output(
|
||||
["git", "-C", str(REPO / "deps/submodules/minirtc"), "rev-parse", "HEAD"], text=True).strip()
|
||||
mini = next(c for c in lock["components"] if c["id"] == "minirtc")
|
||||
if revision != mini["source"]["revision"]:
|
||||
raise ValueError("MiniRTC revision changed; review embedded notices and update sources.json")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--generate", action="store_true", help="download/check pinned sources and regenerate the bundled JSON")
|
||||
parser.add_argument("--cache", type=Path, default=Path.home() / ".cache/crossdesk/licenses")
|
||||
parser.add_argument("--inputs", type=Path, help="native archive .inputs.sha256 manifest")
|
||||
parser.add_argument("--target-info", type=Path, help="resolved Xmake minirtc target information")
|
||||
parser.add_argument("--xmake-repository", type=Path, help="verify the active pinned package repository and recipes")
|
||||
parser.add_argument("--xmake-version", type=Path, help="Xmake version output recorded by the native build")
|
||||
args = parser.parse_args()
|
||||
lock = json.loads(LOCK.read_text())
|
||||
if args.generate:
|
||||
data = generate(lock, args.cache)
|
||||
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT.write_bytes(data)
|
||||
lock["bundle_sha256"] = digest(data)
|
||||
LOCK.write_bytes(json_bytes(lock))
|
||||
verify(lock, args.inputs, args.target_info, args.xmake_repository, args.xmake_version)
|
||||
print(f"Verified {len(lock['components'])} iOS license components")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (OSError, ValueError, KeyError, subprocess.CalledProcessError) as error:
|
||||
print(f"Third-party notices: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Package source-built iOS OpenSSL as a static framework with privacy resources."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import plistlib
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
IOS = Path(__file__).resolve().parents[1]
|
||||
PRIVACY = IOS / "OpenSSL/PrivacyInfo.xcprivacy"
|
||||
# The reviewed iOS configuration removes these calls rather than declaring
|
||||
# unrelated purposes for system entropy devices and certificate directories.
|
||||
FORBIDDEN_APIS = {
|
||||
"stat", "fstat", "lstat", "fstatat", "stat64", "fstat64", "lstat64",
|
||||
"getattrlist", "fgetattrlist", "getattrlistbulk", "getdirentriesattr",
|
||||
"statfs", "fstatfs", "statvfs", "fstatvfs", "getfsstat",
|
||||
"mach_absolute_time", "mach_continuous_time", "sysctl",
|
||||
}
|
||||
|
||||
|
||||
def package(inputs, output):
|
||||
catalog = json.loads((IOS / "CrossDeskMobile/Resources/ThirdPartyLicenses.json").read_text())
|
||||
component = next(c for c in catalog["components"] if c["id"] == "openssl3")
|
||||
archives = {}
|
||||
for line in inputs.read_text().splitlines():
|
||||
sha, name = line.split(maxsplit=1)
|
||||
path = Path(name)
|
||||
if path.name in {"libssl.a", "libcrypto.a"}:
|
||||
if path.name in archives:
|
||||
raise ValueError(f"Ambiguous OpenSSL input: {path.name}")
|
||||
if hashlib.sha256(path.read_bytes()).hexdigest() != sha:
|
||||
raise ValueError(f"OpenSSL input checksum mismatch: {path}")
|
||||
archives[path.name] = (path, sha)
|
||||
if set(archives) != {"libssl.a", "libcrypto.a"}:
|
||||
raise ValueError("Both source-built OpenSSL archives must be present")
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix="openssl-framework-", dir=output.parent) as temp:
|
||||
binary = Path(temp) / "OpenSSL"
|
||||
subprocess.run(["/usr/bin/libtool", "-static", "-o", str(binary),
|
||||
*(str(archives[name][0]) for name in sorted(archives))], check=True)
|
||||
subprocess.run(["xcrun", "lipo", str(binary), "-verify_arch", "arm64"], check=True)
|
||||
symbols = subprocess.check_output(["xcrun", "nm", "-u", str(binary)], text=True)
|
||||
imports = {line.split()[-1].lstrip("_").split("$")[0]
|
||||
for line in symbols.splitlines() if line.strip() and not line.endswith(":")}
|
||||
unexpected = imports & FORBIDDEN_APIS
|
||||
if unexpected:
|
||||
raise ValueError(f"OpenSSL privacy configuration has unreviewed APIs: {sorted(unexpected)}")
|
||||
# armcap.c queries only CPU instruction support/brand to select crypto
|
||||
# implementations. This is not boot time, disk space or fingerprinting.
|
||||
member = ""
|
||||
for line in symbols.splitlines():
|
||||
if line.endswith(":"):
|
||||
member = line
|
||||
elif "_sysctlbyname" in line and member != "libcrypto-lib-armcap.o:":
|
||||
raise ValueError(f"Unreviewed OpenSSL sysctl caller: {member}")
|
||||
# getrandom uses Darwin's getentropy via OpenSSL's existing DSO lookup;
|
||||
# the random device fallback is excluded at compile time.
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
files = {
|
||||
"OpenSSL": binary.read_bytes(),
|
||||
"PrivacyInfo.xcprivacy": PRIVACY.read_bytes(),
|
||||
"Info.plist": plistlib.dumps({
|
||||
"CFBundleIdentifier": "cn.crossdesk.OpenSSL",
|
||||
"CFBundleName": "OpenSSL",
|
||||
"CFBundleExecutable": "OpenSSL",
|
||||
"CFBundlePackageType": "FMWK",
|
||||
"CFBundleInfoDictionaryVersion": "6.0",
|
||||
"CFBundleVersion": component["version"],
|
||||
"CFBundleShortVersionString": component["version"],
|
||||
"CFBundleSupportedPlatforms": ["iPhoneOS"],
|
||||
"MinimumOSVersion": "16.0",
|
||||
}),
|
||||
"LICENSE.txt": "\n\n".join(d["text"] for d in component["documents"]).encode(),
|
||||
"BuildMetadata.json": (json.dumps({
|
||||
"version": component["version"],
|
||||
"sourceURL": component["sourceURL"],
|
||||
"buildSourceURL": component["buildSourceURL"],
|
||||
"archives": {name: sha for name, (_, sha) in sorted(archives.items())},
|
||||
"privacySHA256": hashlib.sha256(PRIVACY.read_bytes()).hexdigest(),
|
||||
}, indent=2) + "\n").encode(),
|
||||
}
|
||||
for name, data in files.items():
|
||||
target = output / name
|
||||
if not target.exists() or target.read_bytes() != data:
|
||||
target.write_bytes(data)
|
||||
print(f"Packaged source-built OpenSSL {component['version']}: {output}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--inputs", required=True, type=Path)
|
||||
parser.add_argument("--output", required=True, type=Path)
|
||||
args = parser.parse_args()
|
||||
package(args.inputs, args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (OSError, ValueError, KeyError, StopIteration, subprocess.CalledProcessError) as error:
|
||||
print(f"OpenSSL framework: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Record the source corresponding to an iOS build without changing the checkout."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
from urllib.parse import quote
|
||||
|
||||
REPO = Path(__file__).resolve().parents[3]
|
||||
REPOSITORY_URL = "https://github.com/kunkundi/crossdesk"
|
||||
MINIRTC_URL = "https://github.com/kunkundi/minirtc"
|
||||
|
||||
|
||||
def git(*args, root=REPO):
|
||||
return subprocess.check_output(["git", "-C", str(root), *args], text=True).strip()
|
||||
|
||||
|
||||
def source_metadata(require_release=False, tag=None):
|
||||
revision = git("rev-parse", "HEAD")
|
||||
dirty = bool(git("status", "--porcelain", "--untracked-files=normal", "--ignore-submodules=none"))
|
||||
tags = git("tag", "--points-at", "HEAD").splitlines()
|
||||
if tag:
|
||||
if tag not in tags:
|
||||
raise ValueError(f"Source tag {tag!r} does not point to the build commit")
|
||||
else:
|
||||
# 'latest' is a moving alias, never a corresponding-source identifier.
|
||||
versions = sorted(t for t in tags if t.startswith("v"))
|
||||
tag = versions[0] if versions else None
|
||||
if require_release and (dirty or not tag or not tag.startswith("v")):
|
||||
raise ValueError("Release source must be a clean checkout of a version tag, with matching submodules")
|
||||
mini_revision = git("rev-parse", "HEAD", root=REPO / "deps/submodules/minirtc")
|
||||
lock_path = REPO / "apps/ios/licenses/sources.json"
|
||||
lock = json.loads(lock_path.read_text())
|
||||
ref = quote(tag if tag and not dirty else revision, safe="")
|
||||
return {
|
||||
"schemaVersion": 1,
|
||||
"repositoryURL": REPOSITORY_URL,
|
||||
"revision": revision,
|
||||
"tag": tag,
|
||||
"isModified": dirty,
|
||||
"sourceURL": f"{REPOSITORY_URL}/tree/{ref}",
|
||||
"buildInstructionsURL": f"{REPOSITORY_URL}/blob/{ref}/apps/ios/README.md",
|
||||
"privacyPolicyURL": f"{REPOSITORY_URL}/blob/{ref}/PRIVACY.md",
|
||||
"miniRTCRevision": mini_revision,
|
||||
"miniRTCSourceURL": f"{MINIRTC_URL}/tree/{mini_revision}",
|
||||
"licenseCatalogSHA256": lock["bundle_sha256"],
|
||||
"sourceCatalogSHA256": hashlib.sha256(lock_path.read_bytes()).hexdigest(),
|
||||
"toolchain": lock["toolchain"],
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--output", required=True, type=Path)
|
||||
parser.add_argument("--tag", default=os.environ.get("CROSSDESK_SOURCE_TAG") or None)
|
||||
parser.add_argument("--require-release", action="store_true",
|
||||
default=os.environ.get("CROSSDESK_RELEASE_BUILD") == "YES")
|
||||
args = parser.parse_args()
|
||||
metadata = source_metadata(args.require_release, args.tag)
|
||||
data = (json.dumps(metadata, ensure_ascii=False, indent=2) + "\n").encode()
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not args.output.exists() or args.output.read_bytes() != data:
|
||||
args.output.write_bytes(data)
|
||||
print(f"Recorded iOS source: {metadata['revision']} (modified: {metadata['isModified']})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (OSError, ValueError, subprocess.CalledProcessError) as error:
|
||||
print(f"iOS source metadata: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check release resources and known restricted imports in a built iOS app."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import plistlib
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def verify(app, require_release=False):
|
||||
info = plistlib.loads((app / "Info.plist").read_bytes())
|
||||
binary = app / info["CFBundleExecutable"]
|
||||
subprocess.run(["xcrun", "lipo", str(binary), "-verify_arch", "arm64"], check=True)
|
||||
symbols = subprocess.check_output(["xcrun", "nm", "-u", str(binary)], text=True)
|
||||
imports = set(line.split()[-1] for line in symbols.splitlines() if line.strip())
|
||||
disk_apis = {"_statfs", "_fstatfs", "_statvfs", "_fstatvfs", "_getfsstat"}
|
||||
if imports & disk_apis:
|
||||
raise ValueError(f"Unreviewed disk-space APIs remain in iOS app: {sorted(imports & disk_apis)}")
|
||||
defined = subprocess.check_output(["xcrun", "nm", "-g", str(binary)], text=True)
|
||||
if re.search(r"\b_Wels(?:CreateDecoder|CreateSVCEncoder|GetCodecVersion)\b", defined):
|
||||
raise ValueError("OpenH264 must not be present in the iOS app")
|
||||
if re.search(r"\b_of_(?:hweight\w*|hw8table|popcount_3)\b", defined):
|
||||
raise ValueError("OpenFEC's optional CC-BY-SA-3.0 hamming-weight code requires a separate distribution review")
|
||||
privacy = plistlib.loads((app / "PrivacyInfo.xcprivacy").read_bytes())
|
||||
reasons = {entry["NSPrivacyAccessedAPIType"]: entry["NSPrivacyAccessedAPITypeReasons"]
|
||||
for entry in privacy["NSPrivacyAccessedAPITypes"]}
|
||||
required = {
|
||||
"NSPrivacyAccessedAPICategoryUserDefaults": {"CA92.1"},
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp": {"C617.1", "3B52.1"},
|
||||
"NSPrivacyAccessedAPICategorySystemBootTime": {"35F9.1", "8FFB.1"},
|
||||
}
|
||||
for category, codes in required.items():
|
||||
if set(reasons.get(category, [])) != codes:
|
||||
raise ValueError(f"Missing or changed reviewed privacy reasons: {category}")
|
||||
data = (app / "ThirdPartyLicenses.json").read_bytes()
|
||||
catalog = json.loads(data)
|
||||
ids = {c["id"] for c in catalog["components"]}
|
||||
if not {"crossdesk", "minirtc", "openfec"} <= ids or "openh264" in ids:
|
||||
raise ValueError("Unexpected iOS license catalog")
|
||||
openssl = next(c for c in catalog["components"] if c["id"] == "openssl3")
|
||||
framework = app / "Frameworks/OpenSSL.framework"
|
||||
sdk_info = plistlib.loads((framework / "Info.plist").read_bytes())
|
||||
sdk_privacy_data = (framework / "PrivacyInfo.xcprivacy").read_bytes()
|
||||
sdk_privacy = plistlib.loads(sdk_privacy_data)
|
||||
expected_privacy = {
|
||||
"NSPrivacyTracking": False,
|
||||
"NSPrivacyTrackingDomains": [],
|
||||
"NSPrivacyCollectedDataTypes": [],
|
||||
"NSPrivacyAccessedAPITypes": [],
|
||||
}
|
||||
if sdk_privacy != expected_privacy:
|
||||
raise ValueError("OpenSSL SDK privacy declaration differs from the reviewed iOS configuration")
|
||||
sdk_build = json.loads((framework / "BuildMetadata.json").read_text())
|
||||
if (sdk_info["CFBundleShortVersionString"] != openssl["version"]
|
||||
or sdk_build["version"] != openssl["version"]
|
||||
or sdk_build["sourceURL"] != openssl["sourceURL"]
|
||||
or sdk_build["buildSourceURL"] != openssl["buildSourceURL"]
|
||||
or sdk_build["privacySHA256"] != hashlib.sha256(sdk_privacy_data).hexdigest()):
|
||||
raise ValueError("OpenSSL SDK resources do not match the source/license catalog")
|
||||
if (framework / "LICENSE.txt").read_text() != "\n\n".join(d["text"] for d in openssl["documents"]):
|
||||
raise ValueError("OpenSSL SDK license resource differs from its source notices")
|
||||
embedded_binary = framework / sdk_info["CFBundleExecutable"]
|
||||
# Xcode removes the archive and may generate a small codeless-framework
|
||||
# stub. It must not ship a second static archive or dynamic OpenSSL copy.
|
||||
if embedded_binary.exists():
|
||||
if embedded_binary.read_bytes().startswith(b"!<arch>\n"):
|
||||
raise ValueError("Xcode must remove the static OpenSSL archive when embedding its resources")
|
||||
sdk_symbols = subprocess.check_output(["xcrun", "nm", "-gU", str(embedded_binary)], text=True)
|
||||
if re.search(r"\b_(?:SSL_new|OPENSSL_init_crypto)\b", sdk_symbols):
|
||||
raise ValueError("Unexpected dynamic OpenSSL implementation in resource framework")
|
||||
linked = subprocess.check_output(["xcrun", "otool", "-L", str(binary)], text=True)
|
||||
if "OpenSSL.framework" in linked:
|
||||
raise ValueError("OpenSSL must be statically linked into the application")
|
||||
source = json.loads((app / "SourceMetadata.json").read_text())
|
||||
if source["licenseCatalogSHA256"] != hashlib.sha256(data).hexdigest():
|
||||
raise ValueError("Bundled licenses differ from the recorded source metadata")
|
||||
if require_release and (source["isModified"] or not source["tag"]):
|
||||
raise ValueError("The app was not built from an unmodified version tag")
|
||||
if not re.fullmatch(r"[0-9a-f]{40}", source["revision"]):
|
||||
raise ValueError("Invalid source revision")
|
||||
print("Verified arm64 app, source/license resources, app privacy declarations and OpenSSL SDK privacy packaging")
|
||||
print("Signing, App Store validation and license authorization require separate review.")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("app", type=Path)
|
||||
parser.add_argument("--require-release", action="store_true")
|
||||
args = parser.parse_args()
|
||||
verify(args.app, args.require_release)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (OSError, ValueError, KeyError, subprocess.CalledProcessError) as error:
|
||||
print(f"iOS bundle verification: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user