From af65d0745648456c80d0682e273a97dfa1fe2f56 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Mon, 17 Jul 2023 18:57:50 -0700 Subject: [PATCH] Support AVB1.0 signing and verification in magiskboot --- native/src/Cargo.lock | 504 +++++++++++++++++++++++++++++++-- native/src/Cargo.toml | 5 + native/src/base/misc.hpp | 2 +- native/src/boot/Cargo.toml | 5 + native/src/boot/bootimg.cpp | 55 +++- native/src/boot/bootimg.hpp | 8 + native/src/boot/lib.rs | 19 ++ native/src/boot/magiskboot.hpp | 2 + native/src/boot/main.cpp | 23 ++ native/src/boot/sign.rs | 273 ++++++++++++++++++ 10 files changed, 865 insertions(+), 31 deletions(-) create mode 100644 native/src/boot/sign.rs diff --git a/native/src/Cargo.lock b/native/src/Cargo.lock index 9105dbd40..37c6ea010 100644 --- a/native/src/Cargo.lock +++ b/native/src/Cargo.lock @@ -74,6 +74,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -133,14 +145,32 @@ dependencies = [ ] [[package]] -name = "cpufeatures" -version = "0.2.8" +name = "const-oid" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" +checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] +[[package]] +name = "crypto-bigint" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -167,7 +197,7 @@ dependencies = [ "codespan-reporting", "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.26", ] [[package]] @@ -180,7 +210,32 @@ version = "1.0.94" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.26", +] + +[[package]] +name = "der" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +dependencies = [ + "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der_derive" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "114792ba6b7545d3f3dd693794aed3a312a67795cd577fcc725c148d84fabe32" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.26", ] [[package]] @@ -190,7 +245,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", ] [[package]] @@ -206,6 +298,22 @@ dependencies = [ "termcolor", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "flagset" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda653ca797810c02f7ca4b804b40b8b95ae046eb989d356bce17919a8c25499" + [[package]] name = "generic-array" version = "0.14.7" @@ -214,6 +322,29 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", ] [[package]] @@ -225,6 +356,24 @@ dependencies = [ "libc", ] +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "humantime" version = "1.3.0" @@ -235,10 +384,25 @@ dependencies = [ ] [[package]] -name = "libc" -version = "0.2.146" +name = "lazy_static" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "log" @@ -266,12 +430,17 @@ dependencies = [ "byteorder", "cxx", "cxx-gen", + "der", "digest", + "p256", + "p384", "pb-rs", "quick-protobuf", + "rsa", "sha1", "sha2", "size", + "x509-cert", ] [[package]] @@ -315,6 +484,23 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -326,6 +512,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -333,6 +540,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", ] [[package]] @@ -347,10 +579,79 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.60" +name = "pem-rfc7468" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primeorder" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -371,18 +672,59 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.28" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" dependencies = [ "proc-macro2", ] [[package]] -name = "regex" -version = "1.8.4" +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" dependencies = [ "aho-corasick", "memchr", @@ -391,9 +733,56 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rsa" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +dependencies = [ + "byteorder", + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "sha2", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] [[package]] name = "sha1" @@ -417,18 +806,56 @@ dependencies = [ "digest", ] +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "size" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fed904c7fb2856d868b92464fc8fa597fce366edea1a9cbfaa8cb5fe080bd6d" +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -442,9 +869,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" dependencies = [ "proc-macro2", "quote", @@ -471,22 +898,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.26", ] [[package]] @@ -497,9 +924,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-width" @@ -519,6 +946,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" @@ -549,3 +982,20 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "x509-cert" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "077a1672d8ecfa5c1fe69fa7c5d043962e4e32866d4375495536261c0b4781f5" +dependencies = [ + "const-oid", + "der", + "spki", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/native/src/Cargo.toml b/native/src/Cargo.toml index c6c37c56a..114b55c71 100644 --- a/native/src/Cargo.toml +++ b/native/src/Cargo.toml @@ -16,6 +16,11 @@ size = "0.4" sha1 = "0.10" sha2 = "0.10" digest = "0.10" +p256 = "0.13" +p384 = "0.13" +rsa = "0.9" +x509-cert = "0.2" +der = "0.7" [workspace.dependencies.argh] git = "https://github.com/topjohnwu/argh.git" diff --git a/native/src/base/misc.hpp b/native/src/base/misc.hpp index eb0739c9f..ab94bc6a5 100644 --- a/native/src/base/misc.hpp +++ b/native/src/base/misc.hpp @@ -140,7 +140,7 @@ struct byte_view { // Bridging to Rust slice byte_view(rust::Slice o) : byte_view(o.data(), o.size()) {} - operator rust::Slice() { return rust::Slice(_buf, _sz); } + operator rust::Slice() const { return rust::Slice(_buf, _sz); } // String as bytes byte_view(const char *s, bool with_nul = true) diff --git a/native/src/boot/Cargo.toml b/native/src/boot/Cargo.toml index 91204bbcf..bcf3b258f 100644 --- a/native/src/boot/Cargo.toml +++ b/native/src/boot/Cargo.toml @@ -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"] } diff --git a/native/src/boot/bootimg.cpp b/native/src/boot/bootimg.cpp index 47df7bfa4..a80a746f0 100644 --- a/native/src/boot/bootimg.cpp +++ b/native/src/boot/bootimg.cpp @@ -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(out.buf() + out.sz() - sizeof(AvbFooter)); - auto vbmeta = reinterpret_cast(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(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(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; } diff --git a/native/src/boot/bootimg.hpp b/native/src/boot/bootimg.hpp index a8f982efa..a8e078e64 100644 --- a/native/src/boot/bootimg.hpp +++ b/native/src/boot/bootimg.hpp @@ -3,6 +3,8 @@ #include #include #include +#include + #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 create_hdr(const uint8_t *addr, format_t type); + + // Rust FFI + rust::Slice get_payload() const { return payload; } + rust::Slice get_tail() const { return tail; } + bool verify(const char *cert = nullptr) const; }; diff --git a/native/src/boot/lib.rs b/native/src/boot/lib.rs index d528fd572..bce450f45 100644 --- a/native/src/boot/lib.rs +++ b/native/src/boot/lib.rs @@ -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; } } diff --git a/native/src/boot/magiskboot.hpp b/native/src/boot/magiskboot.hpp index 22ef9a28d..fbc06c40f 100644 --- a/native/src/boot/magiskboot.hpp +++ b/native/src/boot/magiskboot.hpp @@ -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[]); diff --git a/native/src/boot/main.cpp b/native/src/boot/main.cpp index 3d814dcb4..62c37131e 100644 --- a/native/src/boot/main.cpp +++ b/native/src/boot/main.cpp @@ -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 [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 [name] [x509.pem pk8] + Sign 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 [partition] [outfile] Extract [partition] from 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")) { diff --git a/native/src/boot/sign.rs b/native/src/boot/sign.rs new file mode 100644 index 000000000..8ea58b58c --- /dev/null +++ b/native/src/boot/sign.rs @@ -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), + SHA256withECDSA(P256SigningKey), + SHA384withECDSA(P384SigningKey), +} + +#[allow(clippy::large_enum_variant)] +enum VerifyingKey { + SHA256withRSA(RsaVerifyingKey), + SHA256withECDSA(P256VerifyingKey), + SHA384withECDSA(P384VerifyingKey), +} + +struct Verifier { + digest: Box, + key: VerifyingKey, +} + +impl Verifier { + fn from_public_key(key: SubjectPublicKeyInfoRef) -> LoggedResult { + let digest: Box; + let key = if let Ok(rsa) = RsaPublicKey::try_from(key.clone()) { + digest = Box::::default(); + VerifyingKey::SHA256withRSA(RsaVerifyingKey::::new(rsa)) + } else if let Ok(ec) = P256VerifyingKey::try_from(key.clone()) { + digest = Box::::default(); + VerifyingKey::SHA256withECDSA(ec) + } else if let Ok(ec) = P384VerifyingKey::try_from(key.clone()) { + digest = Box::::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, + key: SigningKey, +} + +impl Signer { + fn from_private_key(key: &[u8]) -> LoggedResult { + let digest: Box; + let key = if let Ok(rsa) = RsaPrivateKey::from_pkcs8_der(key) { + digest = Box::::default(); + SigningKey::SHA256withRSA(RsaSigningKey::::new(rsa)) + } else if let Ok(ec) = P256SigningKey::from_pkcs8_der(key) { + digest = Box::::default(); + SigningKey::SHA256withECDSA(ec) + } else if let Ok(ec) = P384SigningKey::from_pkcs8_der(key) { + digest = Box::::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> { + 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, + 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 { + fn inner( + payload: &[u8], + name: *const c_char, + cert: *const c_char, + key: *const c_char, + ) -> LoggedResult> { + // 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()) +}