iOS隐私清单API检测

iOS隐私清单API检测

背景

在 WWDC23 上苹果引入了常用第三方 SDK 的新隐私声明和签名,并宣布开发者需要在其应用的隐私声明中声明使用一组 API 的批准理由。这些变更有助于开发者更好地了解第三方 SDK 如何使用数据、保护软件依赖关系,并为用户提供额外的隐私保护。

从3月13日开始:如果你向 App Store Connect 上传新应用或更新应用,并且使用了需要批准理由的 API,苹果将通过电子邮件通知您您的应用隐私声明中缺少理由。这是在 App Store Connect 现有通知的基础上增加的。

从5月1日开始:你需要在上传新应用或更新应用至 App Store Connect 时,包括列出的 API 的批准理由。如果您未按允许的理由使用 API,请寻找替代方案。如果您添加了一个位于常用第三方 SDK 列表中的新第三方 SDK,这些 API、隐私声明和签名要求将适用于该 SDK。确保使用包含隐私声明的 SDK 版本,并注意,当 SDK 作为二进制依赖添加时,也需要签名。

参照:

问题以及方案

通过上面的内容我们可以看出:

  • 第三方的SDK需要添加隐私清单,对于活跃的开源库来说开源作者(团队)都已经做好了调整,我们只需要更新一下版本即可,但是对于那些不活跃的开源库来说,我们需要fork一下项目,自行添加隐私清单。当然对于维护私有库的团队来说这也是个体力活,幸好我们可以先优先处理苹果官方清单列举的部分;

  • 当SDK作为二进制依赖(其他方式来看目前并不需要)添加时,需要签名,具体的签名可以参照[WWDC2023视频](Verify app dependencies with digital signatures - WWDC23 - Videos - Apple Developer)。

通过查看具体的隐私清单要求得知,我们主要需要添加两项内容,隐私数据以及API调用,隐私数据和我们之前在App Store审核中的App隐私数据部分是一致的,对于这部分数据似乎只能自行判断并添加相关内容,但是对于API的调用还是可以通过脚本做相应的检测,毕竟我们并不能在编码时还能够记住是否使用了相关的API。

参照:

Privacy manifest files | Apple Developer Documentation

API检测

为了能够保证我们在更新代码之后判断是否有调用相关的隐私清单API,我写了一个脚本用来检测项目中API的调用和PrivacyInfo.xcprivacy文件中的声明是否一致,可以将以下文件放在项目目录下,在终端运行或者添加到Xcode script中。

注意:Xcode执行报错:Operation not permitted,前往Build Settings,User Script Sandboxing值修改为No。

paapi.txt 复制代码
NSPrivacyAccessedAPIType:NSPrivacyAccessedAPICategoryFileTimestamp
NSFileCreationDate
.creationDateKey
NSFileModificationDate
fileModificationDate
NSURLContentModificationDateKey
.contentModificationDateKey
NSURLCreationDateKey
.creationDateKey
getattrlist
getattrlistbulk
fgetattrlist

st_atimespec
st_blksize
st_blocks
st_ctimespec
st_dev
st_flags
st_gen
st_gid
st_ino
st_lspare
st_mode
st_mtimespec
st_nlink
st_qspare
st_rdev
st_size
st_uid

fstat
fstatat
lstat
getattrlistat

NSPrivacyAccessedAPIType:NSPrivacyAccessedAPICategorySystemBootTime
systemUptime
mach_absolute_time

NSPrivacyAccessedAPIType:NSPrivacyAccessedAPICategoryDiskSpace
NSURLVolumeAvailableCapacityKey
.volumeAvailableCapacityKey
NSURLVolumeAvailableCapacityForImportantUsageKey
.volumeAvailableCapacityForImportantUsageKey
NSURLVolumeAvailableCapacityForOpportunisticUsageKey
.volumeAvailableCapacityForOpportunisticUsageKey
NSURLVolumeTotalCapacityKey
.volumeTotalCapacityKey
NSFileSystemFreeSize
.systemFreeSize
NSFileSystemSize
.systemSize
statfs
statvfs
fstatfs
fstatvfs
getattrlist
fgetattrlist
getattrlistat

NSPrivacyAccessedAPIType:NSPrivacyAccessedAPICategoryActiveKeyboards
activeInputModes

NSPrivacyAccessedAPIType:NSPrivacyAccessedAPICategoryUserDefaults
UserDefaults

paapi.txt文件主要包含隐私清单中列举的API

paapidetect.sh 复制代码
#!/bin/bash

# PrivacyInfo.xcprivacy file path
privacy_info_file_path=""
number_of_process=4
# The number of files processed each time
number_of_files=10

# Parsing named parameters
while [[ "$#" -gt 0 ]]; do
    case $1 in
        --ppath|-pp) privacy_info_file_path="$2"; shift ;;
        --nprocess|-np) number_of_process="$2"; shift ;;
        --nfiles|-nf) number_of_files="$2"; shift ;;
        *) echo "Unknown parameter passed: $1"; exit 1 ;;
    esac
    shift
done

# Specify the current directory as the search directory
search_directory="."
api_file_path="paapi.txt"

# Check if the file exists
if [ ! -f "$api_file_path" ]; then
  echo "💥Error: paapi.txt file not found in the current directory."
  exit 1
fi

api_type=""
result_type=""
error_found=0 

# Read each line from the file and perform a search operation
while IFS= read -r search_text; do
  # Check if the search string starts with "NSPrivacyAccessedAPIType:*"
  if [[ $search_text == NSPrivacyAccessedAPIType:* ]]; then
      api_type="${search_text#*:}"
      # Reset result_type when the type changes
      result_type=""
      echo "🌟APIType: ${api_type}🔅"
  else
    # Check if the search string is not empty or does not consist only of spaces
    if [ -n "$(echo "$search_text" | tr -d '[:space:]')" ]; then
      # Process the search string to preserve spaces
      formatted_search_text=$(printf "%s" "$search_text")

      # Initialize an empty string to collect results
      all_results=""
      all_results_echo=""
      # Use find command to search and grep to match the search string
      result=$(find "$search_directory" \( -path "./Pods" -o -path "./Tests" \) -prune -o \
      -type f \( -name "*.h" -o -name "*.m" -o -name "*.mm" -o -name "*.swift" \) \
      -print0 | xargs -0 -P 4 -n 10 grep -H "$search_text")
      if [ -n "$result" ]; then
        # Check if the corresponding Type is in the PrivacyInfo.xcprivacy file
        if [ -z "$privacy_info_file_path" ]; then
          privacy_info_file_path=$(find "$search_directory" \( -path "./Pods" -o -path "./Tests" \) -prune -o \
          -type f -name "*xcprivacy" -print -quit)
        fi

        if [ -n "$privacy_info_file_path" ]; then
          if [ -z "$result_type" ]; then
            # Assign value when result_type is empty
            result_type=$(grep -H "$api_type" "$privacy_info_file_path")
          fi
        fi
        # Accumulate results
        all_results="$all_results$result"
        
        # Accumulate result output
        if [ -n "$all_results_echo" ]; then
          # Only add a newline if all_results is not empty
          all_results_echo="${all_results_echo}\n"
        fi
        all_results_echo="$all_results_echo$result"
      fi

      # Check if any results were accumulated
      if [ -n "$all_results" ]; then
        echo "🔥Files using '${search_text}':"
        echo "$all_results_echo"
        if [ -z "$result_type" ]; then
          error_found=1
          echo "💥Error: PrivacyInfo.xcprivacy file did not include NSPrivacyAccessedAPIType:${api_type}."
        else
          echo "🍀Success: PrivacyInfo.xcprivacy has included NSPrivacyAccessedAPIType:${api_type}."
        fi
      else
        echo "💨'${search_text}' was not used."
      fi
    fi
  fi
done < "$api_file_path"

# Check if any errors were found
if [ "$error_found" -eq 1 ]; then
  exit 1
fi

paapidetect.sh检测项目中是否调用隐私清单中的API并检查PrivacyInfo.xcprivacy文件中是否有包含对应的NSPrivacyAccessedAPIType

  • 支持输出相关隐私清单API是否调用以及输出调用的部分;

  • 支持检测PrivacyInfo.xcprivacy文件中是否有包含对应的Type,对应Reason需要自行判断;

  • 支持设置调用参数。

    • --ppath-pp设置PrivacyInfo.xcprivacy文件相对路径,如果不设置会默认查找项目目录下的首个.xcprivacy文件,因此此脚本并不适合检测有多个.xcprivacy文件的项目,例如你想一次性检测工程中使用pod导入的所有依赖库。当然并不建议如此操作,此脚本更建议放在各个依赖库下,检测工作交给各依赖库来做,而且脚本中排除了Pod以及Test文件夹的扫描。当然如果你仅仅是想看一下工程以及依赖库下对于隐私API的使用,可以移除脚本中的( -path "./Pods" -o -path "./Tests" ) -prune -o

    • --nprocess-np设置进程个数,--nfiles-nf设置一次扫描文件个数。

参照

iOS17 隐私协议适配详解 - 掘金

相关推荐
叽哥1 天前
Flutter Riverpod上手指南
android·flutter·ios
用户092 天前
SwiftUI Charts 函数绘图完全指南
ios·swiftui·swift
YungFan2 天前
iOS26适配指南之UIColor
ios·swift
权咚3 天前
阿权的开发经验小集
git·ios·xcode
用户093 天前
TipKit与CloudKit同步完全指南
ios·swift
法的空间3 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
2501_915918413 天前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview
00后程序员张3 天前
iOS App 混淆与加固对比 源码混淆与ipa文件混淆的区别、iOS代码保护与应用安全场景最佳实践
android·安全·ios·小程序·uni-app·iphone·webview
Magnetic_h3 天前
【iOS】设计模式复习
笔记·学习·ios·设计模式·objective-c·cocoa
00后程序员张4 天前
详细解析苹果iOS应用上架到App Store的完整步骤与指南
android·ios·小程序·https·uni-app·iphone·webview