feat: load v2 apk signature block
This commit is contained in:
225
Cargo.lock
generated
225
Cargo.lock
generated
@ -48,21 +48,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
@ -127,7 +112,7 @@ checksum = "1cb37b529daddddf129612580831db21a538d1aa2798b367039e9316762c81a7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"quick-xml 0.26.0",
|
||||
"quick-xml",
|
||||
"rasn",
|
||||
"rasn-pkix",
|
||||
"roxmltree",
|
||||
@ -151,24 +136,6 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522"
|
||||
dependencies = [
|
||||
"bzip2",
|
||||
"deflate64",
|
||||
"flate2",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"xz2",
|
||||
"zstd",
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.86"
|
||||
@ -211,22 +178,6 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async_zip"
|
||||
version = "0.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b9f7252833d5ed4b00aa9604b563529dd5e11de9c23615de2dcdf91eb87b52"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"chrono",
|
||||
"crc32fast",
|
||||
"futures-lite",
|
||||
"pin-project",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-destructor"
|
||||
version = "0.3.0"
|
||||
@ -403,27 +354,6 @@ version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.12+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72ebc2f1a417f01e1da30ef264ee86ae31d2dcd2d603ea283d3c244a883ca2a9"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
@ -439,8 +369,6 @@ version = "1.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
@ -480,10 +408,7 @@ version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -679,12 +604,6 @@ version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f"
|
||||
|
||||
[[package]]
|
||||
name = "deflate64"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.6.1"
|
||||
@ -910,19 +829,6 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
@ -1214,29 +1120,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
@ -1461,15 +1344,6 @@ version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
@ -1545,17 +1419,6 @@ version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "lzma-sys"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
@ -1602,7 +1465,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"apk",
|
||||
"async-trait",
|
||||
"async_zip",
|
||||
"byteorder",
|
||||
"clap",
|
||||
"config",
|
||||
"dialoguer",
|
||||
@ -1610,7 +1473,6 @@ dependencies = [
|
||||
"indicatif",
|
||||
"log",
|
||||
"nostr-sdk",
|
||||
"quick-xml 0.37.2",
|
||||
"reqwest",
|
||||
"semver",
|
||||
"serde",
|
||||
@ -1859,12 +1721,6 @@ dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.5.0"
|
||||
@ -1961,26 +1817,6 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
@ -2079,16 +1915,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.37.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
@ -2889,7 +2715,6 @@ checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
@ -3259,15 +3084,6 @@ dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
@ -3443,15 +3259,6 @@ version = "0.13.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
|
||||
|
||||
[[package]]
|
||||
name = "xz2"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
|
||||
dependencies = [
|
||||
"lzma-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust2"
|
||||
version = "0.9.0"
|
||||
@ -3568,31 +3375,3 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
"flate2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.13+zstd.1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
@ -21,10 +21,9 @@ tokio = { version = "1.43.0", features = ["fs", "rt", "macros", "rt-multi-thread
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
async-trait = "0.1.86"
|
||||
apk = "0.4.0"
|
||||
async_zip = { version = "0.0.17", features = ["full", "tokio"] }
|
||||
semver = "1.0.25"
|
||||
indicatif = "0.17.11"
|
||||
dialoguer = "0.11.0"
|
||||
env_logger = "0.11.6"
|
||||
sha2 = "0.10.8"
|
||||
quick-xml = { version = "0.37.2", features = ["serialize"] }
|
||||
byteorder = "1.5.0"
|
||||
|
8
nap.yaml
8
nap.yaml
@ -8,15 +8,18 @@ name: "Freeflow"
|
||||
description: "Live in the moment"
|
||||
|
||||
# Application icon
|
||||
icon: "https://freeflow.app/icon.png"
|
||||
icon: "https://nostr.download/b7f6bd1e3951185e49a04f733de61d1658b710645a40a875d5d18fdbcadf85cd.webp"
|
||||
|
||||
# Banner / Preview of the app
|
||||
images:
|
||||
- "https://freeflow.app/banner.jpg"
|
||||
- "https://nostr.download/b6120e8aff11395bca8e51d720e9f4af636ed240dfe04d3b32f5f89cb5966eab.webp"
|
||||
|
||||
# Public code repo or project website
|
||||
repository: "https://github.com/nostrlabs-io/freeflow"
|
||||
|
||||
# Public project website
|
||||
url: "https://freeflow.app"
|
||||
|
||||
# SPDX code license
|
||||
license: "MIT"
|
||||
|
||||
@ -24,3 +27,4 @@ license: "MIT"
|
||||
tags:
|
||||
- "tiktok"
|
||||
- "shorts"
|
||||
- "video"
|
@ -5,10 +5,10 @@ use crate::manifest::Manifest;
|
||||
use crate::repo::Repo;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use clap::Parser;
|
||||
use config::{Config, File, FileSourceFile};
|
||||
use config::{Config, File};
|
||||
use log::info;
|
||||
use nostr_sdk::prelude::Coordinate;
|
||||
use nostr_sdk::{Client, EventBuilder, JsonUtil, Keys, Kind, Tag};
|
||||
use nostr_sdk::{Client, EventBuilder, Keys, Kind, Tag};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
@ -100,7 +100,7 @@ async fn main() -> Result<()> {
|
||||
client.add_relay(r).await?;
|
||||
}
|
||||
if args.relay.is_empty() {
|
||||
const DEFAULT_RELAY: &'static str = "wss://relay.zapstore.dev";
|
||||
const DEFAULT_RELAY: &str = "wss://relay.zapstore.dev";
|
||||
info!("Connecting to default relay {DEFAULT_RELAY}");
|
||||
client.add_relay(DEFAULT_RELAY).await?;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use nostr_sdk::{Event, EventBuilder, Kind, Tag};
|
||||
use nostr_sdk::{EventBuilder, Kind, Tag};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -9,12 +9,18 @@ pub struct Manifest {
|
||||
/// Application display name
|
||||
pub name: String,
|
||||
|
||||
/// Long app description / release notes
|
||||
/// App description
|
||||
pub description: Option<String>,
|
||||
|
||||
/// Long form app description (with markdown)
|
||||
pub summary: Option<String>,
|
||||
|
||||
/// Repo URL
|
||||
pub repository: Option<String>,
|
||||
|
||||
/// Public project website
|
||||
pub url: Option<String>,
|
||||
|
||||
/// SPDX license code
|
||||
pub license: Option<String>,
|
||||
|
||||
@ -28,32 +34,43 @@ pub struct Manifest {
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
impl Into<EventBuilder> for &Manifest {
|
||||
fn into(self) -> EventBuilder {
|
||||
let mut b = EventBuilder::new(
|
||||
Kind::Custom(32_267),
|
||||
self.description.clone().unwrap_or_default(),
|
||||
)
|
||||
impl From<&Manifest> for EventBuilder {
|
||||
fn from(val: &Manifest) -> Self {
|
||||
let mut b = EventBuilder::new(Kind::Custom(32_267), val.description.as_str_or_empty())
|
||||
.tags([
|
||||
Tag::parse(["d", &self.id]).unwrap(),
|
||||
Tag::parse(["name", &self.name]).unwrap(),
|
||||
Tag::parse(["d", &val.id]).unwrap(),
|
||||
Tag::parse(["name", &val.name]).unwrap(),
|
||||
Tag::parse(["url", val.url.as_str_or_empty()]).unwrap(),
|
||||
]);
|
||||
if let Some(icon) = &self.icon {
|
||||
if let Some(s) = &val.summary {
|
||||
b = b.tag(Tag::parse(["summary", s]).unwrap());
|
||||
}
|
||||
if let Some(icon) = &val.icon {
|
||||
b = b.tag(Tag::parse(["icon", icon]).unwrap());
|
||||
}
|
||||
if let Some(repository) = &self.repository {
|
||||
if let Some(repository) = &val.repository {
|
||||
b = b.tag(Tag::parse(["repository", repository]).unwrap());
|
||||
}
|
||||
if let Some(license) = &self.license {
|
||||
if let Some(license) = &val.license {
|
||||
b = b.tag(Tag::parse(["license", license]).unwrap());
|
||||
}
|
||||
for image in &self.images {
|
||||
for image in &val.images {
|
||||
b = b.tag(Tag::parse(["image", image]).unwrap());
|
||||
}
|
||||
for tag in &self.tags {
|
||||
for tag in &val.tags {
|
||||
b = b.tag(Tag::parse(["t", tag]).unwrap());
|
||||
}
|
||||
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AsStrOrEmpty {
|
||||
fn as_str_or_empty(&self) -> &str;
|
||||
}
|
||||
|
||||
impl AsStrOrEmpty for Option<String> {
|
||||
fn as_str_or_empty(&self) -> &str {
|
||||
self.as_ref().map(|s| s.as_str()).unwrap_or("")
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::repo::{
|
||||
load_artifact, load_artifact_url, Repo, RepoArtifact, RepoRelease, RepoResource,
|
||||
load_artifact_url, Repo, RepoRelease,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use log::{info, warn};
|
||||
|
435
src/repo/mod.rs
435
src/repo/mod.rs
@ -1,26 +1,24 @@
|
||||
use crate::manifest::Manifest;
|
||||
use crate::repo::github::GithubRepo;
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use apk::manifest::Sdk;
|
||||
use anyhow::{anyhow, bail, ensure, Result};
|
||||
use apk::res::Chunk;
|
||||
use apk::zip::ZipArchive;
|
||||
use apk::AndroidManifest;
|
||||
use async_zip::tokio::read::seek::ZipFileReader;
|
||||
use async_zip::ZipFile;
|
||||
use byteorder::LittleEndian;
|
||||
use byteorder::ReadBytesExt;
|
||||
use log::{debug, info, warn};
|
||||
use nostr_sdk::async_utility::futures_util::TryStreamExt;
|
||||
use nostr_sdk::prelude::{hex, Coordinate, StreamExt};
|
||||
use nostr_sdk::{Event, EventBuilder, Kind, NostrSigner, Tag};
|
||||
use reqwest::Url;
|
||||
use semver::Version;
|
||||
use serde::Deserialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env::temp_dir;
|
||||
use std::fmt::{write, Display, Formatter};
|
||||
use std::io::Cursor;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncBufRead, AsyncRead, AsyncReadExt, AsyncSeek, AsyncWriteExt, BufReader};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
mod github;
|
||||
|
||||
@ -73,7 +71,31 @@ impl TryInto<EventBuilder> for RepoArtifact {
|
||||
b = b.tag(Tag::parse(["url", u.as_str()])?);
|
||||
}
|
||||
match self.metadata {
|
||||
ArtifactMetadata::APK { manifest } => {
|
||||
ArtifactMetadata::APK {
|
||||
manifest,
|
||||
signature,
|
||||
} => {
|
||||
match signature {
|
||||
ApkSignatureBlock::None => {
|
||||
warn!("No signature found in metadata");
|
||||
}
|
||||
ApkSignatureBlock::V2 { signatures, .. } => {
|
||||
for signature in signatures {
|
||||
b = b.tag(Tag::parse([
|
||||
"apk_signature_hash",
|
||||
&hex::encode(signature.digest),
|
||||
])?);
|
||||
}
|
||||
}
|
||||
ApkSignatureBlock::V3 { signatures, .. } => {
|
||||
for signature in signatures {
|
||||
b = b.tag(Tag::parse([
|
||||
"apk_signature_hash",
|
||||
&hex::encode(signature.digest),
|
||||
])?);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(vn) = manifest.version_name {
|
||||
b = b.tag(Tag::parse(["version", vn.as_str()])?);
|
||||
}
|
||||
@ -101,19 +123,127 @@ impl TryInto<EventBuilder> for RepoArtifact {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ArtifactMetadata {
|
||||
APK { manifest: AndroidManifest },
|
||||
APK {
|
||||
manifest: AndroidManifest,
|
||||
signature: ApkSignatureBlock,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ApkSignatureBlock {
|
||||
None,
|
||||
/// Android V2 Signature Block
|
||||
///
|
||||
/// https://source.android.com/docs/security/features/apksigning/v2#apk-signature-scheme-v2-block-format
|
||||
V2 {
|
||||
signatures: Vec<ApkSignature>,
|
||||
public_key: Vec<u8>,
|
||||
certificates: Vec<Vec<u8>>,
|
||||
attributes: HashMap<u32, Vec<u8>>,
|
||||
},
|
||||
V3 {
|
||||
signatures: Vec<ApkSignature>,
|
||||
public_key: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Display for ApkSignatureBlock {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ApkSignatureBlock::None => write!(f, "none"),
|
||||
ApkSignatureBlock::V2 { signatures, .. } => {
|
||||
write!(f, "v2: ")?;
|
||||
for sig in signatures {
|
||||
write!(
|
||||
f,
|
||||
"algo={}, digest={}, sig={}",
|
||||
sig.algo,
|
||||
hex::encode(&sig.digest),
|
||||
hex::encode(&sig.signature)
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ApkSignatureBlock::V3 { signatures, .. } => {
|
||||
write!(f, "V3: ")?;
|
||||
for sig in signatures {
|
||||
write!(
|
||||
f,
|
||||
"algo={}, digest={}, sig={}",
|
||||
sig.algo,
|
||||
hex::encode(&sig.digest),
|
||||
hex::encode(&sig.signature)
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ApkSignature {
|
||||
pub algo: ApkSignatureAlgo,
|
||||
pub signature: Vec<u8>,
|
||||
pub digest: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ApkSignatureAlgo {
|
||||
RsaSsaPssSha256,
|
||||
RsaSsaPssSha512,
|
||||
RsaSsaPkcs1Sha256,
|
||||
RsaSsaPkcs1Sha512,
|
||||
EcdsaSha256,
|
||||
EcdsaSha512,
|
||||
DsaSha256,
|
||||
}
|
||||
|
||||
impl Display for ApkSignatureAlgo {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ApkSignatureAlgo::RsaSsaPssSha256 => write!(f, "RSASSA-PSS-SHA256"),
|
||||
ApkSignatureAlgo::RsaSsaPssSha512 => write!(f, "RSASSA-PSS-SHA512"),
|
||||
ApkSignatureAlgo::RsaSsaPkcs1Sha256 => write!(f, "RSASSA-PKCS1-SHA256"),
|
||||
ApkSignatureAlgo::RsaSsaPkcs1Sha512 => write!(f, "RSASSA-PKCS1-SHA512"),
|
||||
ApkSignatureAlgo::EcdsaSha256 => write!(f, "ECDSA-SHA256"),
|
||||
ApkSignatureAlgo::EcdsaSha512 => write!(f, "ECDSA-SHA512"),
|
||||
ApkSignatureAlgo::DsaSha256 => write!(f, "DSA-SHA256"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for ApkSignatureAlgo {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
0x0101 => Ok(ApkSignatureAlgo::RsaSsaPssSha256),
|
||||
0x0102 => Ok(ApkSignatureAlgo::RsaSsaPssSha512),
|
||||
0x0103 => Ok(ApkSignatureAlgo::RsaSsaPkcs1Sha256),
|
||||
0x0104 => Ok(ApkSignatureAlgo::RsaSsaPkcs1Sha512),
|
||||
0x0201 => Ok(ApkSignatureAlgo::EcdsaSha256),
|
||||
0x0202 => Ok(ApkSignatureAlgo::EcdsaSha512),
|
||||
0x0301 => Ok(ApkSignatureAlgo::DsaSha256),
|
||||
_ => bail!("Unknown signature algo"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ArtifactMetadata {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ArtifactMetadata::APK { manifest } => {
|
||||
ArtifactMetadata::APK {
|
||||
manifest,
|
||||
signature,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"APK id={}, version={}, code={}",
|
||||
"APK id={}, version={}, code={}, sig={}",
|
||||
manifest.package.as_ref().unwrap_or(&"missing".to_string()),
|
||||
manifest.version_name.as_ref().unwrap_or(&String::new()),
|
||||
manifest.version_code.as_ref().unwrap_or(&0)
|
||||
manifest.version_code.as_ref().unwrap_or(&0),
|
||||
signature
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -233,7 +363,7 @@ impl RepoRelease {
|
||||
self.artifacts
|
||||
.iter()
|
||||
.find_map(|a| match &a.metadata {
|
||||
ArtifactMetadata::APK { manifest } if manifest.package.is_some() => {
|
||||
ArtifactMetadata::APK { manifest, .. } if manifest.package.is_some() => {
|
||||
Some(manifest.package.as_ref().unwrap().to_string())
|
||||
}
|
||||
_ => None,
|
||||
@ -243,7 +373,7 @@ impl RepoRelease {
|
||||
|
||||
/// [app_id]@[version]
|
||||
pub fn release_tag(&self) -> Result<String> {
|
||||
Ok(format!("{}@{}", self.app_id()?, self.version.to_string()))
|
||||
Ok(format!("{}@{}", self.app_id()?, self.version))
|
||||
}
|
||||
|
||||
/// Create nostr release artifact list event
|
||||
@ -255,13 +385,16 @@ impl RepoRelease {
|
||||
let mut ret = vec![];
|
||||
let mut b = EventBuilder::new(
|
||||
Kind::Custom(30063),
|
||||
self.description.as_ref().map(|s| s.as_str()).unwrap_or(""),
|
||||
self.description.as_deref().unwrap_or(""),
|
||||
)
|
||||
.tags([
|
||||
Tag::coordinate(app_coord),
|
||||
Tag::parse(["d", &self.release_tag()?])?,
|
||||
]);
|
||||
|
||||
if let Some(url) = self.url {
|
||||
b = b.tag(Tag::parse(["url", &url])?);
|
||||
}
|
||||
for a in &self.artifacts {
|
||||
let eb: Result<EventBuilder> = a.clone().try_into();
|
||||
match eb {
|
||||
@ -317,7 +450,7 @@ async fn load_artifact_url(url: &str) -> Result<RepoArtifact> {
|
||||
.unwrap(),
|
||||
);
|
||||
if !tmp.exists() {
|
||||
let mut tmp_file = File::create(&tmp).await?;
|
||||
let mut tmp_file = tokio::fs::File::create(&tmp).await?;
|
||||
let mut rsp_stream = rsp.bytes_stream();
|
||||
while let Some(data) = rsp_stream.next().await {
|
||||
if let Ok(data) = data {
|
||||
@ -325,37 +458,38 @@ async fn load_artifact_url(url: &str) -> Result<RepoArtifact> {
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut a = load_artifact(&tmp).await?;
|
||||
let mut a = load_artifact(&tmp)?;
|
||||
// replace location back to URL for publishing
|
||||
a.location = RepoResource::Remote(url.to_string());
|
||||
Ok(a)
|
||||
}
|
||||
|
||||
async fn load_artifact(path: &Path) -> Result<RepoArtifact> {
|
||||
fn load_artifact(path: &Path) -> Result<RepoArtifact> {
|
||||
match path
|
||||
.extension()
|
||||
.ok_or(anyhow!("missing file extension"))?
|
||||
.to_str()
|
||||
.unwrap()
|
||||
{
|
||||
"apk" => load_apk_artifact(path).await,
|
||||
"apk" => load_apk_artifact(path),
|
||||
v => bail!("unknown file extension: {v}"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_apk_artifact(path: &Path) -> Result<RepoArtifact> {
|
||||
let file = File::open(path).await?;
|
||||
fn load_apk_artifact(path: &Path) -> Result<RepoArtifact> {
|
||||
let file = std::fs::File::open(path)?;
|
||||
let mut file = std::io::BufReader::new(file);
|
||||
let sig_block = load_signing_block(&mut file)?;
|
||||
|
||||
let mut zip = ZipFileReader::with_tokio(BufReader::new(file)).await?;
|
||||
let manifest = load_manifest(&mut zip).await?;
|
||||
let mut zip = ZipArchive::new(file)?;
|
||||
let manifest = load_manifest(&mut zip)?;
|
||||
|
||||
let lib_arch: HashSet<String> = list_libs(&mut zip)
|
||||
.iter()
|
||||
.filter_map(|p| {
|
||||
PathBuf::from(p)
|
||||
.iter()
|
||||
.skip(1)
|
||||
.next()
|
||||
.nth(1)
|
||||
.map(|p| p.to_str().unwrap().to_owned())
|
||||
})
|
||||
.collect();
|
||||
@ -366,7 +500,7 @@ async fn load_apk_artifact(path: &Path) -> Result<RepoArtifact> {
|
||||
name: path.file_name().unwrap().to_str().unwrap().to_string(),
|
||||
size: path.metadata()?.len(),
|
||||
location: RepoResource::Local(path.to_path_buf()),
|
||||
hash: Some(hash_file(&path).await?),
|
||||
hash: Some(hash_file(path)?),
|
||||
content_type: "application/vnd.android.package-archive".to_string(),
|
||||
platform: Platform::Android {
|
||||
arch: match lib_arch.iter().next().unwrap().as_str() {
|
||||
@ -377,15 +511,18 @@ async fn load_apk_artifact(path: &Path) -> Result<RepoArtifact> {
|
||||
v => bail!("unknown architecture: {v}"),
|
||||
},
|
||||
},
|
||||
metadata: ArtifactMetadata::APK { manifest },
|
||||
metadata: ArtifactMetadata::APK {
|
||||
manifest,
|
||||
signature: sig_block.try_into()?,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async fn hash_file(path: &Path) -> Result<Vec<u8>> {
|
||||
let mut file = File::open(path).await?;
|
||||
fn hash_file(path: &Path) -> Result<Vec<u8>> {
|
||||
let mut file = File::open(path)?;
|
||||
let mut hash = Sha256::default();
|
||||
let mut buf = Vec::with_capacity(4096);
|
||||
while let Ok(r) = file.read(&mut buf).await {
|
||||
while let Ok(r) = file.read(&mut buf) {
|
||||
if r == 0 {
|
||||
break;
|
||||
}
|
||||
@ -394,42 +531,201 @@ async fn hash_file(path: &Path) -> Result<Vec<u8>> {
|
||||
Ok(hash.finalize().to_vec())
|
||||
}
|
||||
|
||||
async fn load_manifest<T>(zip: &mut ZipFileReader<T>) -> Result<AndroidManifest>
|
||||
fn load_manifest<T>(zip: &mut ZipArchive<T>) -> Result<AndroidManifest>
|
||||
where
|
||||
T: AsyncBufRead + AsyncSeek + Unpin,
|
||||
T: Read + Seek,
|
||||
{
|
||||
const ANDROID_MANIFEST: &'static str = "AndroidManifest.xml";
|
||||
const ANDROID_MANIFEST: &str = "AndroidManifest.xml";
|
||||
|
||||
let idx = zip
|
||||
.file()
|
||||
.entries()
|
||||
let mut f = zip.by_name(ANDROID_MANIFEST)?;
|
||||
let mut manifest_data = Vec::with_capacity(8192);
|
||||
let r = f.read_to_end(&mut manifest_data)?;
|
||||
let res: AndroidManifest = parse_android_manifest(&manifest_data[..r])?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ApkSigningBlock {
|
||||
pub data: Vec<(u32, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl TryInto<ApkSignatureBlock> for ApkSigningBlock {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_into(self) -> std::result::Result<ApkSignatureBlock, Self::Error> {
|
||||
const V2_SIG_BLOCK_ID: u32 = 0x7109871a;
|
||||
const V3_SIG_BLOCK_ID: u32 = 0xf05368c0;
|
||||
|
||||
if let Some(v3) =
|
||||
self.data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, entry)| {
|
||||
if entry.filename().as_bytes() == ANDROID_MANIFEST.as_bytes() {
|
||||
Some(i)
|
||||
.find_map(|(k, v)| if *k == V3_SIG_BLOCK_ID { Some(v) } else { None })
|
||||
{
|
||||
todo!("Not done yet")
|
||||
}
|
||||
if let Some(v2) =
|
||||
self.data
|
||||
.iter()
|
||||
.find_map(|(k, v)| if *k == V2_SIG_BLOCK_ID { Some(v) } else { None })
|
||||
{
|
||||
let v2 = get_length_prefixed_u32_sequence(&v2[4..])?;
|
||||
let signed_data = get_sequence(v2[0])?;
|
||||
let digests = get_sequence_kv(signed_data[0])?;
|
||||
let certificates = get_sequence(signed_data[1])?;
|
||||
let attributes = get_sequence_kv(signed_data[2])?;
|
||||
let signatures = get_sequence_kv(v2[1])?;
|
||||
let public_key = v2[2];
|
||||
let digests: HashMap<u32, &[u8]> = HashMap::from_iter(digests);
|
||||
return Ok(ApkSignatureBlock::V2 {
|
||||
attributes: HashMap::from_iter(
|
||||
attributes.into_iter().map(|(k, v)| (k, v.to_vec())),
|
||||
),
|
||||
certificates: certificates.into_iter().map(|v| v.to_vec()).collect(),
|
||||
signatures: signatures
|
||||
.into_iter()
|
||||
.filter_map(|(k, v)| {
|
||||
let sig_len = u32::from_le_bytes(v[..4].try_into().ok()?) as usize;
|
||||
if sig_len > v.len() - 4 {
|
||||
warn!("Invalid signature length: {} > {}", sig_len, v.len());
|
||||
return None;
|
||||
}
|
||||
if let Ok(a) = ApkSignatureAlgo::try_from(k) {
|
||||
Some(ApkSignature {
|
||||
algo: a,
|
||||
digest: digests.get(&k).map(|v| v[4..].to_vec())?,
|
||||
signature: v[4..sig_len + 4].to_vec(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or(anyhow!("missing AndroidManifest file"))?;
|
||||
let mut manifest = zip.reader_with_entry(idx).await?;
|
||||
let mut manifest_data = Vec::with_capacity(8192);
|
||||
manifest.read_to_end_checked(&mut manifest_data).await?;
|
||||
let res: AndroidManifest = parse_android_manifest(&manifest_data)?;
|
||||
Ok(res)
|
||||
.collect(),
|
||||
public_key: public_key.to_vec(),
|
||||
});
|
||||
}
|
||||
Ok(ApkSignatureBlock::None)
|
||||
}
|
||||
}
|
||||
|
||||
fn list_libs<T>(zip: &mut ZipFileReader<T>) -> Vec<String>
|
||||
fn load_signing_block<R>(zip: &mut R) -> Result<ApkSigningBlock>
|
||||
where
|
||||
T: AsyncBufRead + AsyncSeek + Unpin,
|
||||
R: Read + Seek,
|
||||
{
|
||||
zip.file()
|
||||
.entries()
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
if entry.filename().as_bytes().starts_with(b"lib/") {
|
||||
Some(entry.filename().as_str().unwrap().to_owned())
|
||||
const SIG_BLOCK_MAGIC: &[u8] = b"APK Sig Block 42";
|
||||
|
||||
// scan backwards until we find the singing block
|
||||
let flen = zip.seek(SeekFrom::End(0))?;
|
||||
let mut magic_buf = [0u8; 16];
|
||||
loop {
|
||||
let magic_pos = zip.seek(SeekFrom::Current(-17))?;
|
||||
if magic_pos <= 4 {
|
||||
bail!("Failed to find signing block");
|
||||
}
|
||||
|
||||
zip.read_exact(&mut magic_buf)?;
|
||||
if magic_buf == SIG_BLOCK_MAGIC {
|
||||
zip.seek(SeekFrom::Current(-(16 + 8)))?;
|
||||
let size1 = zip.read_u64::<LittleEndian>()?;
|
||||
ensure!(size1 <= flen, "Signing block is larger than entire file");
|
||||
|
||||
zip.seek(SeekFrom::Current(-(size1 as i64 - 8)))?;
|
||||
let size2 = zip.read_u64::<LittleEndian>()?;
|
||||
ensure!(
|
||||
size2 == size1,
|
||||
"Invalid block sizes, {} != {}",
|
||||
size1,
|
||||
size2
|
||||
);
|
||||
|
||||
let mut data_bytes = size1 - 8 - 16;
|
||||
let mut sigs = Vec::new();
|
||||
loop {
|
||||
let (k, v) = read_u64_length_prefixed_kv(zip)?;
|
||||
data_bytes -= (v.len() + 4 + 8) as u64;
|
||||
sigs.push((k, v));
|
||||
if data_bytes == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
zip.seek(SeekFrom::Start(0))?;
|
||||
return Ok(ApkSigningBlock { data: sigs });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read_u64_length_prefixed_kv<T>(file: &mut T) -> Result<(u32, Vec<u8>)>
|
||||
where
|
||||
T: Read + Seek,
|
||||
{
|
||||
let kv_len = file.read_u64::<LittleEndian>()?;
|
||||
let k = file.read_u32::<LittleEndian>()?;
|
||||
let v_len = kv_len as usize - 4;
|
||||
let mut v = vec![0; v_len];
|
||||
file.read_exact(v.as_mut_slice())?;
|
||||
Ok((k, v))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_u64_length_prefixed_kv(slice: &[u8]) -> Result<(u32, &[u8])> {
|
||||
let kv_len = u64::from_le_bytes(slice[..8].try_into()?);
|
||||
let k = u32::from_le_bytes(slice[8..12].try_into()?);
|
||||
Ok((k, &slice[12..(kv_len as usize - 12)]))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_u32_length_prefixed_kv(slice: &[u8]) -> Result<(u32, &[u8])> {
|
||||
let kv_len = u32::from_le_bytes(slice[..4].try_into()?);
|
||||
let k = u32::from_le_bytes(slice[4..8].try_into()?);
|
||||
Ok((k, &slice[8..(kv_len as usize - 8)]))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_length_prefixed_u32(slice: &[u8]) -> Result<&[u8]> {
|
||||
let len = u32::from_le_bytes(slice[..4].try_into()?);
|
||||
Ok(&slice[4..4 + len as usize])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_length_prefixed_u32_sequence(slice: &[u8]) -> Result<Vec<&[u8]>> {
|
||||
let sequence_len = u32::from_le_bytes(slice[..4].try_into()?);
|
||||
get_sequence(&slice[4..4 + sequence_len as usize])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_sequence(mut slice: &[u8]) -> Result<Vec<&[u8]>> {
|
||||
let mut ret = Vec::new();
|
||||
while slice.len() >= 4 {
|
||||
let data = get_length_prefixed_u32(slice)?;
|
||||
let r_len = data.len() + 4;
|
||||
slice = &slice[r_len..];
|
||||
ret.push(data);
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_sequence_kv(slice: &[u8]) -> Result<Vec<(u32, &[u8])>> {
|
||||
let seq = get_sequence(slice)?;
|
||||
Ok(seq
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let k = u32::from_le_bytes(s[..4].try_into().unwrap());
|
||||
let v = &s[4..];
|
||||
(k, v)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn list_libs<T>(zip: &mut ZipArchive<T>) -> Vec<String>
|
||||
where
|
||||
T: Read + Seek,
|
||||
{
|
||||
zip.file_names()
|
||||
.filter_map(|f| {
|
||||
if f.starts_with("lib/") {
|
||||
Some(f.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -437,7 +733,7 @@ where
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_android_manifest(data: &Vec<u8>) -> Result<AndroidManifest> {
|
||||
fn parse_android_manifest(data: &[u8]) -> Result<AndroidManifest> {
|
||||
let chunks = if let Chunk::Xml(chunks) = Chunk::parse(&mut Cursor::new(data))? {
|
||||
chunks
|
||||
} else {
|
||||
@ -528,3 +824,26 @@ fn find_value_in(
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn read_apk() -> Result<()> {
|
||||
let path = "/home/kieran/Downloads/app-arm64-v8a-release.apk";
|
||||
|
||||
let apk = load_apk_artifact(&PathBuf::from(path))?;
|
||||
assert!(
|
||||
matches!(&apk.platform, Platform::Android { arch } if matches!(arch, Architecture::ARM64 { .. }))
|
||||
);
|
||||
assert!(matches!(&apk.metadata,
|
||||
ArtifactMetadata::APK { signature, .. } if matches!(signature,
|
||||
ApkSignatureBlock::V2 { signatures, .. } if signatures.len() == 1 &&
|
||||
matches!(signatures[0].algo, ApkSignatureAlgo::RsaSsaPkcs1Sha256))));
|
||||
|
||||
eprint!("{}", apk);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user