field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

release-evidence.sh (8527B)


      1 #!/bin/sh
      2 set -eu
      3 
      4 repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
      5 mode=${1:-}
      6 case "$mode" in
      7     write|check) ;;
      8     *) echo "usage: $0 {write|check}" >&2; exit 64 ;;
      9 esac
     10 
     11 for tool in cargo jq shasum
     12 do
     13     command -v "$tool" >/dev/null 2>&1 || {
     14         echo "error: required release-evidence tool is unavailable: $tool" >&2
     15         exit 1
     16     }
     17 done
     18 
     19 tmp_root=$(mktemp -d "${TMPDIR:-/tmp}/tera-ios-release.XXXXXX")
     20 trap 'rm -rf "$tmp_root"' EXIT HUP INT TERM
     21 metadata="$tmp_root/cargo-metadata.json"
     22 rendered_sbom="$tmp_root/sbom.cdx.json"
     23 rendered_provenance="$tmp_root/provenance.json"
     24 
     25 cargo metadata --locked --format-version 1 > "$metadata"
     26 
     27 version=$(jq -er '
     28     [.packages[] | select(.name == "tera_ffi") | .version]
     29     | if length == 1 then .[0] else error("missing owned FFI package") end
     30 ' "$metadata")
     31 
     32 jq -S --slurpfile swift "$repo_root/Package.resolved" \
     33     --arg version "$version" '
     34     def cargo_ref:
     35         "cargo:" + .name + "@" + .version + "?source=" + (.source // "workspace");
     36     def swift_ref:
     37         "swift:" + .identity + "@" + .state.revision + "?source=" + .location;
     38     def cargo_components:
     39         [.packages[] |
     40             {
     41                 "bom-ref": cargo_ref,
     42                 type: "library",
     43                 name: .name,
     44                 version: .version,
     45                 licenses: (
     46                     if .license == null then [] else [{expression: .license}] end
     47                 ),
     48                 properties: [
     49                     {name: "radroots.package.manager", value: "cargo"},
     50                     {name: "radroots.package.source", value: (.source // "workspace")}
     51                 ]
     52             }
     53         ];
     54     def swift_components:
     55         [$swift[0].pins[] |
     56             {
     57                 "bom-ref": swift_ref,
     58                 type: "library",
     59                 name: .identity,
     60                 version: .state.revision,
     61                 properties: [
     62                     {name: "radroots.package.manager", value: "swiftpm"},
     63                     {name: "radroots.package.source", value: .location},
     64                     {name: "radroots.package.revision", value: .state.revision}
     65                 ]
     66             }
     67         ];
     68     . as $cargo |
     69     ($cargo.packages |
     70         map({key: .id, value: cargo_ref}) |
     71         from_entries
     72     ) as $cargo_refs |
     73     (cargo_components + swift_components | sort_by(."bom-ref")) as $components |
     74     ("pkg:generic/tera@" + $version) as $application_ref |
     75     (
     76         [$cargo.resolve.nodes[] |
     77             {
     78                 ref: $cargo_refs[.id],
     79                 dependsOn: ([.dependencies[] as $dependency | $cargo_refs[$dependency]] | sort)
     80             }
     81         ] +
     82         [$swift[0].pins[] | {ref: swift_ref, dependsOn: []}] +
     83         [{
     84             ref: $application_ref,
     85             dependsOn: (
     86                 ([$cargo.workspace_members[] as $member | $cargo_refs[$member]] +
     87                  [$swift[0].pins[] | swift_ref]) |
     88                 sort
     89             )
     90         }] |
     91         sort_by(.ref)
     92     ) as $dependencies |
     93     {
     94         bomFormat: "CycloneDX",
     95         specVersion: "1.5",
     96         version: 1,
     97         metadata: {
     98             component: {
     99                 "bom-ref": $application_ref,
    100                 type: "application",
    101                 name: "tera",
    102                 version: $version,
    103                 purl: ("pkg:generic/tera@" + $version),
    104                 externalReferences: [{
    105                     type: "vcs",
    106                     url: "https://github.com/radrootslabs/tera"
    107                 }]
    108             }
    109         },
    110         components: $components,
    111         dependencies: $dependencies
    112     }
    113 ' "$metadata" > "$rendered_sbom"
    114 
    115 hash_file() {
    116     shasum -a 256 "$1" | awk '{print $1}'
    117 }
    118 
    119 lib_revision=$(jq -er '.foundation.revision' "$repo_root/TeraFFI/source/aarch64-apple-ios.json")
    120 source_date_epoch=$(jq -er '.build.source_date_epoch' "$repo_root/TeraFFI/source/aarch64-apple-ios.json")
    121 tera_ffi_source_tree=$(jq -er '.candidate.source.tree' "$repo_root/TeraFFI/provenance.json")
    122 consumer_lock_sha256=$(hash_file "$repo_root/radroots.lib.source-lock.v1.toml")
    123 cargo_lock_sha256=$(hash_file "$repo_root/Cargo.lock")
    124 swift_lock_sha256=$(hash_file "$repo_root/Package.resolved")
    125 xcode_swift_lock_sha256=$(hash_file \
    126     "$repo_root/Tera.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved")
    127 ffi_provenance_sha256=$(hash_file "$repo_root/TeraFFI/provenance.json")
    128 ffi_api_sha256=$(hash_file "$repo_root/TeraFFI/api/TeraKitBindings.symbols.json")
    129 app_api_sha256=$(hash_file "$repo_root/api/TeraApp.symbols.json")
    130 info_plist_sha256=$(hash_file "$repo_root/Tera/Info.plist")
    131 privacy_manifest_sha256=$(hash_file "$repo_root/Tera/Resources/PrivacyInfo.xcprivacy")
    132 xcode_project_sha256=$(hash_file "$repo_root/Tera.xcodeproj/project.pbxproj")
    133 sbom_sha256=$(hash_file "$rendered_sbom")
    134 
    135 jq -nS \
    136     --arg version "$version" \
    137     --arg lib_revision "$lib_revision" \
    138     --arg tera_ffi_source_tree "$tera_ffi_source_tree" \
    139     --argjson source_date_epoch "$source_date_epoch" \
    140     --arg consumer_lock_sha256 "$consumer_lock_sha256" \
    141     --arg cargo_lock_sha256 "$cargo_lock_sha256" \
    142     --arg swift_lock_sha256 "$swift_lock_sha256" \
    143     --arg xcode_swift_lock_sha256 "$xcode_swift_lock_sha256" \
    144     --arg ffi_provenance_sha256 "$ffi_provenance_sha256" \
    145     --arg ffi_api_sha256 "$ffi_api_sha256" \
    146     --arg app_api_sha256 "$app_api_sha256" \
    147     --arg info_plist_sha256 "$info_plist_sha256" \
    148     --arg privacy_manifest_sha256 "$privacy_manifest_sha256" \
    149     --arg xcode_project_sha256 "$xcode_project_sha256" \
    150     --arg sbom_sha256 "$sbom_sha256" '
    151     {
    152         schema: "tera.release-provenance.v1",
    153         product: "tera",
    154         version: $version,
    155         repository: "https://github.com/radrootslabs/tera",
    156         source: {
    157             lib_revision: $lib_revision,
    158             tera_ffi_source_tree: $tera_ffi_source_tree,
    159             source_date_epoch: $source_date_epoch,
    160             consumer_source_lock_sha256: $consumer_lock_sha256,
    161             cargo_lock_sha256: $cargo_lock_sha256,
    162             swift_package_lock_sha256: $swift_lock_sha256,
    163             xcode_package_lock_sha256: $xcode_swift_lock_sha256
    164         },
    165         artifacts: {
    166             ffi_provenance_sha256: $ffi_provenance_sha256,
    167             ffi_api_sha256: $ffi_api_sha256,
    168             app_api_sha256: $app_api_sha256,
    169             info_plist_sha256: $info_plist_sha256,
    170             privacy_manifest_sha256: $privacy_manifest_sha256,
    171             xcode_project_sha256: $xcode_project_sha256,
    172             sbom_sha256: $sbom_sha256
    173         },
    174         platforms: ["ios-arm64", "ios-arm64-simulator"],
    175         disposition: "unsigned"
    176     }
    177 ' > "$rendered_provenance"
    178 
    179 jq -e '
    180     .bomFormat == "CycloneDX" and
    181     .specVersion == "1.5" and
    182     .version == 1 and
    183     .metadata.component.name == "tera" and
    184     (.components | length > 0) and
    185     (([.components[]."bom-ref"] | unique | length) == (.components | length)) and
    186     ([.components[]."bom-ref"] == ([.components[]."bom-ref"] | sort)) and
    187     ([.dependencies[].ref] == ([.dependencies[].ref] | sort)) and
    188     (.dependencies | all(.dependsOn == (.dependsOn | sort))) and
    189     (.components | all(
    190         (.name | type == "string" and length > 0) and
    191         (.version | type == "string" and length > 0)
    192     ))
    193 ' "$rendered_sbom" >/dev/null
    194 jq -e \
    195     --arg lib_revision "$lib_revision" \
    196     --arg tera_ffi_source_tree "$tera_ffi_source_tree" \
    197     --arg sbom_sha256 "$sbom_sha256" '
    198     .schema == "tera.release-provenance.v1" and
    199     .repository == "https://github.com/radrootslabs/tera" and
    200     .source.lib_revision == $lib_revision and
    201     .source.tera_ffi_source_tree == $tera_ffi_source_tree and
    202     .artifacts.sbom_sha256 == $sbom_sha256 and
    203     .platforms == ["ios-arm64", "ios-arm64-simulator"] and
    204     .disposition == "unsigned"
    205 ' "$rendered_provenance" >/dev/null
    206 
    207 if rg -n '/Users/|/Volumes/|BEGIN [A-Z ]*PRIVATE KEY' \
    208     "$rendered_sbom" "$rendered_provenance"
    209 then
    210     echo "error: release evidence contains forbidden host or protected material" >&2
    211     exit 1
    212 fi
    213 
    214 output_root="$repo_root/release"
    215 case "$mode" in
    216     write)
    217         mkdir -p "$output_root"
    218         install -m 0644 "$rendered_sbom" "$output_root/sbom.cdx.json"
    219         install -m 0644 "$rendered_provenance" "$output_root/provenance.json"
    220         ;;
    221     check)
    222         cmp "$rendered_sbom" "$output_root/sbom.cdx.json"
    223         cmp "$rendered_provenance" "$output_root/provenance.json"
    224         ;;
    225 esac
    226 
    227 echo "release evidence $mode succeeded for tera@$version"