Support AVB1.0 signing and verification in magiskboot

This commit is contained in:
topjohnwu
2023-07-17 18:57:50 -07:00
parent 16d728f379
commit af65d07456
10 changed files with 865 additions and 31 deletions

View File

@@ -21,3 +21,8 @@ argh = { workspace = true }
sha1 = { workspace = true }
sha2 = { workspace = true }
digest = { workspace = true }
p256 = { workspace = true }
p384 = { workspace = true }
rsa = { workspace = true, features = ["sha2"] }
x509-cert = { workspace = true }
der = { workspace = true, features = ["derive"] }

View File

@@ -451,6 +451,12 @@ bool boot_img::parse_image(const uint8_t *p, format_t type) {
flags[LG_BUMP_FLAG] = true;
}
// Check if the image is signed
if (verify()) {
fprintf(stderr, "AVB1_SIGNED\n");
flags[AVB1_SIGNED_FLAG] = true;
}
// Find AVB footer
const void *footer = tail.buf() + tail.sz() - sizeof(AvbFooter);
if (BUFFER_MATCH(footer, AVB_FOOTER_MAGIC)) {
@@ -469,6 +475,10 @@ bool boot_img::parse_image(const uint8_t *p, format_t type) {
return true;
}
bool boot_img::verify(const char *cert) const {
return rust::verify_boot_image(*this, cert);
}
int split_image_dtb(const char *filename) {
mmap_data img(filename);
@@ -740,8 +750,6 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
}
}
close(fd);
/******************
* Patch the image
******************/
@@ -810,11 +818,11 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
if (boot.flags[AVB_FLAG]) {
// Copy and patch AVB structures
auto footer = reinterpret_cast<AvbFooter*>(out.buf() + out.sz() - sizeof(AvbFooter));
auto vbmeta = reinterpret_cast<AvbVBMetaImageHeader*>(out.buf() + off.vbmeta);
memcpy(footer, boot.avb_footer, sizeof(AvbFooter));
footer->original_image_size = __builtin_bswap64(off.total);
footer->vbmeta_offset = __builtin_bswap64(off.vbmeta);
if (check_env("PATCHVBMETAFLAG")) {
auto vbmeta = reinterpret_cast<AvbVBMetaImageHeader*>(out.buf() + off.vbmeta);
vbmeta->flags = __builtin_bswap32(3);
}
}
@@ -831,4 +839,45 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
auto b_hdr = reinterpret_cast<blob_hdr *>(out.buf());
b_hdr->size = off.total - sizeof(blob_hdr);
}
// Sign the image after we finish patching the boot image
if (boot.flags[AVB1_SIGNED_FLAG]) {
byte_view payload(out.buf() + off.header, off.total - off.header);
auto sig = rust::sign_boot_image(payload, "/boot", nullptr, nullptr);
if (!sig.empty()) {
lseek(fd, off.total, SEEK_SET);
xwrite(fd, sig.data(), sig.size());
}
}
close(fd);
}
int verify(const char *image, const char *cert) {
const boot_img boot(image);
if (cert == nullptr) {
// Boot image parsing already checks if the image is signed
return boot.flags[AVB1_SIGNED_FLAG] ? 0 : 1;
} else {
// Provide a custom certificate and re-verify
return boot.verify(cert) ? 0 : 1;
}
}
int sign(const char *image, const char *name, const char *cert, const char *key) {
const boot_img boot(image);
auto sig = rust::sign_boot_image(boot.payload, name, cert, key);
if (sig.empty())
return 1;
auto eof = boot.tail.buf() - boot.map.buf();
int fd = xopen(image, O_WRONLY | O_CLOEXEC);
if (lseek(fd, eof, SEEK_SET) != eof || xwrite(fd, sig.data(), sig.size()) != sig.size()) {
close(fd);
return 1;
}
// Wipe out rest of tail
write_zero(fd, boot.map.sz() - lseek(fd, 0, SEEK_CUR));
close(fd);
return 0;
}

View File

@@ -3,6 +3,8 @@
#include <stdint.h>
#include <utility>
#include <bitset>
#include <cxx.h>
#include "format.hpp"
/******************
@@ -578,6 +580,7 @@ enum {
NOOKHD_FLAG,
ACCLAIM_FLAG,
AMONET_FLAG,
AVB1_SIGNED_FLAG,
AVB_FLAG,
ZIMAGE_KERNEL,
BOOT_FLAGS_MAX
@@ -655,4 +658,9 @@ struct boot_img {
bool parse_image(const uint8_t *addr, format_t type);
const std::pair<const uint8_t *, dyn_img_hdr *> create_hdr(const uint8_t *addr, format_t type);
// Rust FFI
rust::Slice<const uint8_t> get_payload() const { return payload; }
rust::Slice<const uint8_t> get_tail() const { return tail; }
bool verify(const char *cert = nullptr) const;
};

View File

@@ -1,11 +1,14 @@
#![feature(format_args_nl)]
#![feature(btree_drain_filter)]
extern crate alloc;
pub use base;
use cpio::cpio_commands;
use patch::{hexpatch, patch_encryption, patch_verity};
use payload::extract_boot_from_payload;
use sha::{get_sha, sha1_hash, sha256_hash, SHA};
use sign::{sign_boot_image, verify_boot_image};
mod cpio;
mod patch;
@@ -15,12 +18,21 @@ mod payload;
mod proto;
mod ramdisk;
mod sha;
mod sign;
#[cxx::bridge]
pub mod ffi {
unsafe extern "C++" {
include!("compress.hpp");
fn decompress(buf: &[u8], fd: i32) -> bool;
include!("bootimg.hpp");
#[rust_name = "BootImage"]
type boot_img;
#[rust_name = "payload"]
fn get_payload(self: &BootImage) -> &[u8];
#[rust_name = "tail"]
fn get_tail(self: &BootImage) -> &[u8];
}
extern "Rust" {
@@ -46,5 +58,12 @@ pub mod ffi {
) -> bool;
unsafe fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool;
unsafe fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool;
unsafe fn sign_boot_image(
payload: &[u8],
name: *const c_char,
cert: *const c_char,
key: *const c_char,
) -> Vec<u8>;
}
}

View File

@@ -16,6 +16,8 @@
int unpack(const char *image, bool skip_decomp = false, bool hdr = false);
void repack(const char *src_img, const char *out_img, bool skip_comp = false);
int verify(const char *image, const char *cert);
int sign(const char *image, const char *name, const char *cert, const char *key);
int split_image_dtb(const char *filename);
int dtb_commands(int argc, char *argv[]);

View File

@@ -45,6 +45,20 @@ Supported actions:
If env variable PATCHVBMETAFLAG is set to true, all disable flags in
the boot image's vbmeta header will be set.
verify <bootimg> [x509.pem]
Check whether the boot image is signed with AVB 1.0 signature.
Optionally provide a certificate to verify whether the image is
signed by the public key certificate.
Return value:
0:valid 1:error
sign <bootimg> [name] [x509.pem pk8]
Sign <bootimg> with AVB 1.0 signature.
Optionally provide the name of the image (default: '/boot').
Optionally provide the certificate/private key pair for signing.
If the certificate/private key pair is not provided, the AOSP
verity key bundled in the executable will be used.
extract <payload.bin> [partition] [outfile]
Extract [partition] from <payload.bin> to [outfile].
If [outfile] is not specified, then output to '[partition].img'.
@@ -171,6 +185,15 @@ int main(int argc, char *argv[]) {
} else {
repack(argv[2], argv[3] ? argv[3] : NEW_BOOT);
}
} else if (argc > 2 && action == "verify") {
return verify(argv[2], argv[3]);
} else if (argc > 2 && action == "sign") {
if (argc == 5) usage(argv[0]);
return sign(
argv[2],
argc > 3 ? argv[3] : "/boot",
argc > 5 ? argv[4] : nullptr,
argc > 5 ? argv[5] : nullptr);
} else if (argc > 2 && action == "decompress") {
decompress(argv[2], argv[3]);
} else if (argc > 2 && str_starts(action, "compress")) {

273
native/src/boot/sign.rs Normal file
View File

@@ -0,0 +1,273 @@
use der::referenced::OwnedToRef;
use der::{Decode, DecodePem, Encode, Sequence, SliceReader};
use digest::DynDigest;
use p256::ecdsa::{
Signature as P256Signature, SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey,
};
use p256::pkcs8::DecodePrivateKey;
use p384::ecdsa::{
Signature as P384Signature, SigningKey as P384SigningKey, VerifyingKey as P384VerifyingKey,
};
use rsa::pkcs1v15::{
Signature as RsaSignature, SigningKey as RsaSigningKey, VerifyingKey as RsaVerifyingKey,
};
use rsa::pkcs8::SubjectPublicKeyInfoRef;
use rsa::signature::hazmat::{PrehashSigner, PrehashVerifier};
use rsa::signature::SignatureEncoding;
use rsa::{RsaPrivateKey, RsaPublicKey};
use sha2::{Sha256, Sha384};
use x509_cert::der::asn1::{OctetString, PrintableString};
use x509_cert::der::Any;
use x509_cert::spki::AlgorithmIdentifier;
use x509_cert::Certificate;
use base::libc::c_char;
use base::{log_err, LoggedResult, MappedFile, ResultExt, StrErr, Utf8CStr};
use crate::ffi::BootImage;
#[allow(clippy::large_enum_variant)]
enum SigningKey {
SHA256withRSA(RsaSigningKey<Sha256>),
SHA256withECDSA(P256SigningKey),
SHA384withECDSA(P384SigningKey),
}
#[allow(clippy::large_enum_variant)]
enum VerifyingKey {
SHA256withRSA(RsaVerifyingKey<Sha256>),
SHA256withECDSA(P256VerifyingKey),
SHA384withECDSA(P384VerifyingKey),
}
struct Verifier {
digest: Box<dyn DynDigest>,
key: VerifyingKey,
}
impl Verifier {
fn from_public_key(key: SubjectPublicKeyInfoRef) -> LoggedResult<Verifier> {
let digest: Box<dyn DynDigest>;
let key = if let Ok(rsa) = RsaPublicKey::try_from(key.clone()) {
digest = Box::<Sha256>::default();
VerifyingKey::SHA256withRSA(RsaVerifyingKey::<Sha256>::new(rsa))
} else if let Ok(ec) = P256VerifyingKey::try_from(key.clone()) {
digest = Box::<Sha256>::default();
VerifyingKey::SHA256withECDSA(ec)
} else if let Ok(ec) = P384VerifyingKey::try_from(key.clone()) {
digest = Box::<Sha384>::default();
VerifyingKey::SHA384withECDSA(ec)
} else {
return Err(log_err!("Unsupported private key"));
};
Ok(Verifier { digest, key })
}
fn update(&mut self, data: &[u8]) {
self.digest.update(data)
}
fn verify(mut self, signature: &[u8]) -> LoggedResult<()> {
let hash = self.digest.finalize_reset();
return match &self.key {
VerifyingKey::SHA256withRSA(key) => {
let sig = RsaSignature::try_from(signature)?;
key.verify_prehash(hash.as_ref(), &sig).log()
}
VerifyingKey::SHA256withECDSA(key) => {
let sig = P256Signature::from_slice(signature)?;
key.verify_prehash(hash.as_ref(), &sig).log()
}
VerifyingKey::SHA384withECDSA(key) => {
let sig = P384Signature::from_slice(signature)?;
key.verify_prehash(hash.as_ref(), &sig).log()
}
};
}
}
struct Signer {
digest: Box<dyn DynDigest>,
key: SigningKey,
}
impl Signer {
fn from_private_key(key: &[u8]) -> LoggedResult<Signer> {
let digest: Box<dyn DynDigest>;
let key = if let Ok(rsa) = RsaPrivateKey::from_pkcs8_der(key) {
digest = Box::<Sha256>::default();
SigningKey::SHA256withRSA(RsaSigningKey::<Sha256>::new(rsa))
} else if let Ok(ec) = P256SigningKey::from_pkcs8_der(key) {
digest = Box::<Sha256>::default();
SigningKey::SHA256withECDSA(ec)
} else if let Ok(ec) = P384SigningKey::from_pkcs8_der(key) {
digest = Box::<Sha384>::default();
SigningKey::SHA384withECDSA(ec)
} else {
return Err(log_err!("Unsupported private key"));
};
Ok(Signer { digest, key })
}
fn update(&mut self, data: &[u8]) {
self.digest.update(data)
}
fn sign(mut self) -> LoggedResult<Vec<u8>> {
let hash = self.digest.finalize_reset();
let v = match &self.key {
SigningKey::SHA256withRSA(key) => {
let sig: RsaSignature = key.sign_prehash(hash.as_ref())?;
sig.to_vec()
}
SigningKey::SHA256withECDSA(key) => {
let sig: P256Signature = key.sign_prehash(hash.as_ref())?;
sig.to_vec()
}
SigningKey::SHA384withECDSA(key) => {
let sig: P384Signature = key.sign_prehash(hash.as_ref())?;
sig.to_vec()
}
};
Ok(v)
}
}
/*
* BootSignature ::= SEQUENCE {
* formatVersion ::= INTEGER,
* certificate ::= Certificate,
* algorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL
* },
* authenticatedAttributes ::= SEQUENCE {
* target CHARACTER STRING,
* length INTEGER
* },
* signature ::= OCTET STRING
* }
*/
#[derive(Sequence)]
struct AuthenticatedAttributes {
target: PrintableString,
length: u64,
}
#[derive(Sequence)]
struct BootSignature {
format_version: i32,
certificate: Certificate,
algorithm_identifier: AlgorithmIdentifier<Any>,
authenticated_attributes: AuthenticatedAttributes,
signature: OctetString,
}
impl BootSignature {
fn verify(self, payload: &[u8]) -> LoggedResult<()> {
if self.authenticated_attributes.length as usize != payload.len() {
return Err(log_err!("Invalid image size"));
}
let mut verifier = Verifier::from_public_key(
self.certificate
.tbs_certificate
.subject_public_key_info
.owned_to_ref(),
)?;
verifier.update(payload);
let attr = self.authenticated_attributes.to_der()?;
verifier.update(attr.as_slice());
verifier.verify(self.signature.as_bytes())?;
Ok(())
}
}
pub fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool {
fn inner(img: &BootImage, cert: *const c_char) -> LoggedResult<()> {
let tail = img.tail();
// Don't use BootSignature::from_der because tail might have trailing zeros
let mut reader = SliceReader::new(tail)?;
let mut sig = BootSignature::decode(&mut reader)?;
match unsafe { Utf8CStr::from_ptr(cert) } {
Ok(s) => {
let pem = MappedFile::open(s)?;
sig.certificate = Certificate::from_pem(pem)?;
}
Err(StrErr::NullPointerError) => {}
Err(e) => Err(e)?,
};
sig.verify(img.payload())?;
Ok(())
}
inner(img, cert).is_ok()
}
enum Bytes {
Mapped(MappedFile),
Slice(&'static [u8]),
}
impl AsRef<[u8]> for Bytes {
fn as_ref(&self) -> &[u8] {
match self {
Bytes::Mapped(m) => m.as_ref(),
Bytes::Slice(s) => s,
}
}
}
const VERITY_PEM: &[u8] = include_bytes!("../../../tools/keys/verity.x509.pem");
const VERITY_PK8: &[u8] = include_bytes!("../../../tools/keys/verity.pk8");
pub fn sign_boot_image(
payload: &[u8],
name: *const c_char,
cert: *const c_char,
key: *const c_char,
) -> Vec<u8> {
fn inner(
payload: &[u8],
name: *const c_char,
cert: *const c_char,
key: *const c_char,
) -> LoggedResult<Vec<u8>> {
// Process arguments
let name = unsafe { Utf8CStr::from_ptr(name) }?;
let cert = match unsafe { Utf8CStr::from_ptr(cert) } {
Ok(s) => Bytes::Mapped(MappedFile::open(s)?),
Err(StrErr::NullPointerError) => Bytes::Slice(VERITY_PEM),
Err(e) => Err(e)?,
};
let key = match unsafe { Utf8CStr::from_ptr(key) } {
Ok(s) => Bytes::Mapped(MappedFile::open(s)?),
Err(StrErr::NullPointerError) => Bytes::Slice(VERITY_PK8),
Err(e) => Err(e)?,
};
// Parse cert and private key
let cert = Certificate::from_pem(cert)?;
let mut signer = Signer::from_private_key(key.as_ref())?;
// Sign image
let attr = AuthenticatedAttributes {
target: PrintableString::new(name.as_bytes())?,
length: payload.len() as u64,
};
signer.update(payload);
signer.update(attr.to_der()?.as_slice());
let sig = signer.sign()?;
// Create BootSignature DER
let alg_id = cert.signature_algorithm.clone();
let sig = BootSignature {
format_version: 1,
certificate: cert,
algorithm_identifier: alg_id,
authenticated_attributes: attr,
signature: OctetString::new(sig)?,
};
sig.to_der().log()
}
inner(payload, name, cert, key).unwrap_or(Vec::new())
}