1use std::fs::File;
4use std::io::{BufWriter, Seek, SeekFrom, Write};
5use std::path::Path;
6
7use super::{
8 BlockId, DigestAlgorithm, KeyKind, Scheme, Schemes, SignError, SigningBlock, SigningIdentity, jar, scheme,
9};
10use crate::archive::{self, ApkLayout, ApkWriter, EntryOptions, ReadAt, ZipError};
11use crate::digest::{ContentDigest, content_digests};
12use crate::error::{Error, FileOperation};
13use crate::native::PageSize;
14use crate::types::{ApiLevel, ArchivePath};
15
16#[derive(Clone, Debug)]
22pub struct Signer {
23 identities: Vec<SigningIdentity>,
24 schemes: Option<Schemes>,
25 min_sdk: Option<ApiLevel>,
26 page_size: PageSize,
27}
28
29impl Signer {
30 pub fn new(identity: SigningIdentity) -> Self {
31 Self { identities: vec![identity], schemes: None, min_sdk: None, page_size: PageSize::SixteenKiB }
32 }
33
34 pub fn with_signer(mut self, identity: SigningIdentity) -> Self {
36 self.identities.push(identity);
37 self
38 }
39
40 pub fn min_sdk(mut self, min_sdk: ApiLevel) -> Self {
44 self.min_sdk = Some(min_sdk);
45 self
46 }
47
48 pub fn schemes(mut self, schemes: Schemes) -> Self {
50 self.schemes = Some(schemes);
51 self
52 }
53
54 pub fn native_library_page_size(mut self, page_size: PageSize) -> Self {
57 self.page_size = page_size;
58 self
59 }
60
61 pub fn identities(&self) -> &[SigningIdentity] {
62 &self.identities
63 }
64
65 pub fn sign_file(&self, path: impl AsRef<Path>) -> Result<(), Error> {
68 self.sign(path.as_ref(), path.as_ref())
69 }
70
71 pub fn sign(&self, input: impl AsRef<Path>, output: impl AsRef<Path>) -> Result<(), Error> {
76 let (input, output) = (input.as_ref(), output.as_ref());
77 let source = File::open(input).map_err(Error::file(FileOperation::Open, input))?;
78 let (mut archive, layout) = archive::open(&source)?;
79 let manifest = crate::apk::read_manifest(&mut archive)?;
80 let min_sdk = manifest.as_ref().map(|m| m.min_sdk().api_level()).transpose()?;
81 let target_sdk = manifest.as_ref().map(|m| m.effective_target_sdk()).transpose()?;
82 let plan = self.plan(min_sdk, target_sdk)?;
83
84 let mut staged = crate::fs::Staged::beside(output)?;
85 let staged_path = staged.path().to_owned();
86 let mut writer = ApkWriter::new(BufWriter::new(staged.file()));
87 if let Some(digest) = plan.jar_digest() {
88 writer = writer.digest_entries(digest);
89 }
90 writer.set_comment(layout.comment())?;
91 let page_size = self.page_size.bytes() as u16;
92 for index in 0..archive.len() {
93 let name = archive::entry_name(&archive, index)?;
94 if archive::is_signature_file(&name) {
95 continue;
96 }
97 let alignment = if name.starts_with("lib/") && name.ends_with(".so") { page_size } else { 4 };
98 writer.copy(&mut archive, index, alignment)?;
99 }
100 for (path, data) in plan.jar_signature_files(writer.digests())? {
101 writer.add(&path, &data, EntryOptions::STORED)?;
102 }
103 writer.finish()?.flush().map_err(Error::file(FileOperation::Write, staged_path))?;
104 plan.apply_block(staged.file(), &preserved_pairs(&source, &layout)?)?;
105 drop(archive);
107 drop(source);
108 staged.persist(output)
109 }
110
111 pub(crate) fn plan(&self, min_sdk: Option<ApiLevel>, target_sdk: Option<ApiLevel>) -> Result<Plan<'_>, SignError> {
115 let min_sdk = self.min_sdk.or(min_sdk).ok_or(SignError::UnknownMinSdk)?;
116 let schemes = self.schemes.unwrap_or_else(|| Schemes::for_min_sdk(min_sdk));
117 schemes.check_coverage(min_sdk, target_sdk.unwrap_or(min_sdk))?;
118 if schemes.contains(Schemes::V3) && self.identities.len() > 1 {
119 return Err(SignError::MultipleV3Signers { count: self.identities.len() });
120 }
121 let has_ec_signer = self.identities.iter().any(|i| !matches!(i.key().kind(), KeyKind::Rsa { .. }));
122 if schemes.contains(Schemes::V1) && min_sdk < ApiLevel::JELLY_BEAN_MR2 && has_ec_signer {
123 return Err(SignError::EcdsaJarRequiresApi18 { min_sdk });
124 }
125 let jar_digest = if min_sdk < ApiLevel::JELLY_BEAN_MR2 || (has_ec_signer && min_sdk < ApiLevel::LOLLIPOP) {
129 DigestAlgorithm::Sha1
130 } else {
131 DigestAlgorithm::Sha256
132 };
133 Ok(Plan { identities: &self.identities, schemes, jar_digest })
134 }
135}
136
137pub(crate) struct Plan<'a> {
139 identities: &'a [SigningIdentity],
140 schemes: Schemes,
141 jar_digest: DigestAlgorithm,
142}
143
144impl Plan<'_> {
145 pub fn jar_digest(&self) -> Option<DigestAlgorithm> {
147 self.schemes.contains(Schemes::V1).then_some(self.jar_digest)
148 }
149
150 pub fn jar_signature_files(&self, digests: &[(String, Vec<u8>)]) -> Result<Vec<(ArchivePath, Vec<u8>)>, SignError> {
154 if !self.schemes.contains(Schemes::V1) {
155 return Ok(Vec::new());
156 }
157 let declared: Vec<Scheme> = self.schemes.iter_schemes().filter(|s| *s != Scheme::V1).collect();
158 jar::sign(digests, self.jar_digest, self.identities, &declared)
159 }
160
161 pub fn apply_block(&self, file: &mut File, preserved: &[(BlockId, Vec<u8>)]) -> Result<(), Error> {
164 let block_schemes: Vec<Scheme> = self.schemes.iter_schemes().filter(|s| *s != Scheme::V1).collect();
165 if block_schemes.is_empty() {
166 return Ok(());
167 }
168 let layout = ApkLayout::read(&mut *file)?;
169 let mut algorithms: Vec<ContentDigest> =
170 self.identities.iter().filter_map(|i| i.key().signature_algorithm().content_digest()).collect();
171 algorithms.sort();
172 algorithms.dedup();
173 let digests = content_digests(&*file, &layout, &algorithms)?;
174 let mut block = SigningBlock::default();
175 for &scheme in &block_schemes {
176 let stronger: Vec<Scheme> = block_schemes.iter().copied().filter(|s| *s > scheme).collect();
177 let value = scheme::sign(scheme, self.identities, &digests, &stronger)?;
178 block.pairs.push((scheme.block_id().expect("block scheme"), value));
179 }
180 block.pairs.extend_from_slice(preserved);
181 let block = block.encode();
182 self_check(&block, &block_schemes).map_err(|e| SignError::SelfCheck(Box::new(e)))?;
183 splice(file, &layout, &block)
184 }
185}
186
187fn preserved_pairs(source: &File, layout: &ApkLayout) -> Result<Vec<(BlockId, Vec<u8>)>, Error> {
190 let Some(range) = &layout.signing_block else { return Ok(Vec::new()) };
191 let bytes = source.read_vec_at(range.start, (range.end - range.start) as usize).map_err(ZipError::Io)?;
192 let block = SigningBlock::parse(&bytes)?;
193 Ok(block.pairs.into_iter().filter(|(id, _)| !id.is_signature_data()).collect())
194}
195
196fn self_check(block: &[u8], schemes: &[Scheme]) -> Result<(), super::VerifyError> {
199 let block = SigningBlock::parse(block)?;
200 for &scheme in schemes {
201 let value = block.get(scheme.block_id().expect("block scheme")).expect("the block was built with every scheme");
202 scheme::verify(scheme, value, &mut |_| Ok(None))?;
203 }
204 Ok(())
205}
206
207fn splice(file: &mut File, layout: &ApkLayout, block: &[u8]) -> Result<(), Error> {
209 let io = |error| Error::from(ZipError::Io(error));
210 let cd = &layout.central_directory;
211 let central_directory = file.read_vec_at(cd.start, (cd.end - cd.start) as usize).map_err(io)?;
212 let eocd = layout.eocd_with_central_directory_at(layout.entries_end + block.len() as u64)?;
213 let mut tail = Vec::with_capacity(block.len() + central_directory.len() + eocd.len());
214 tail.extend_from_slice(block);
215 tail.extend_from_slice(¢ral_directory);
216 tail.extend_from_slice(&eocd);
217 file.seek(SeekFrom::Start(layout.entries_end)).map_err(io)?;
218 file.write_all(&tail).map_err(io)?;
219 file.set_len(layout.entries_end + tail.len() as u64).map_err(io)?;
220 Ok(())
221}