mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat: codesign for deno compile binaries (#24604)
Uses [sui](https://github.com/littledivy/sui) to inject metadata as a custom section in the denort binary. Metadata is stored as a Mach-O segment on macOS and PE `RT_RCDATA` resource on Windows. Fixes #11154 Fixes https://github.com/denoland/deno/issues/17753 ```cpp deno compile app.tsx # on macOS codesign --sign - ./app # on Windows signtool sign /fd SHA256 .\app.exe ``` --------- Signed-off-by: Divy Srivastava <dj.srivastava23@gmail.com>
This commit is contained in:
parent
f57745fe21
commit
5bd76609f7
6 changed files with 109 additions and 69 deletions
34
Cargo.lock
generated
34
Cargo.lock
generated
|
@ -1092,6 +1092,12 @@ version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41b319d1b62ffbd002e057f36bebd1f42b9f97927c9577461d855f3513c4289f"
|
checksum = "41b319d1b62ffbd002e057f36bebd1f42b9f97927c9577461d855f3513c4289f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "debug-ignore"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ffe7ed1d93f4553003e20b629abe9085e1e81b1429520f897f8f8860bc6dfc21"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "debugid"
|
name = "debugid"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -1160,6 +1166,7 @@ dependencies = [
|
||||||
"junction",
|
"junction",
|
||||||
"lazy-regex",
|
"lazy-regex",
|
||||||
"libc",
|
"libc",
|
||||||
|
"libsui",
|
||||||
"libz-sys",
|
"libz-sys",
|
||||||
"log",
|
"log",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
|
@ -2524,6 +2531,19 @@ dependencies = [
|
||||||
"spki",
|
"spki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "editpe"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48cede2bb1b07dd598d269f973792c43e0cd92686d3b452bd6e01d7a8eb01211"
|
||||||
|
dependencies = [
|
||||||
|
"debug-ignore",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"thiserror",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
|
@ -4025,6 +4045,19 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libsui"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c2fedcf6cb4dd935f94a90e1c4300c727fe7112b8455615e902828c7401f84d"
|
||||||
|
dependencies = [
|
||||||
|
"editpe",
|
||||||
|
"libc",
|
||||||
|
"sha2",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libz-sys"
|
name = "libz-sys"
|
||||||
version = "1.1.16"
|
version = "1.1.16"
|
||||||
|
@ -8281,6 +8314,7 @@ version = "0.7.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,7 @@ deno_semver = "=0.5.7"
|
||||||
deno_task_shell = "=0.17.0"
|
deno_task_shell = "=0.17.0"
|
||||||
deno_terminal.workspace = true
|
deno_terminal.workspace = true
|
||||||
eszip = "=0.73.0"
|
eszip = "=0.73.0"
|
||||||
|
libsui = "0.1.0"
|
||||||
napi_sym.workspace = true
|
napi_sym.workspace = true
|
||||||
node_resolver.workspace = true
|
node_resolver.workspace = true
|
||||||
|
|
||||||
|
|
|
@ -82,9 +82,7 @@ fn load_env_vars(env_vars: &HashMap<String, String>) {
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Vec<_> = env::args_os().collect();
|
let args: Vec<_> = env::args_os().collect();
|
||||||
let current_exe_path = current_exe().unwrap();
|
let standalone = standalone::extract_standalone(Cow::Owned(args));
|
||||||
let standalone =
|
|
||||||
standalone::extract_standalone(¤t_exe_path, Cow::Owned(args));
|
|
||||||
let future = async move {
|
let future = async move {
|
||||||
match standalone {
|
match standalone {
|
||||||
Ok(Some(future)) => {
|
Ok(Some(future)) => {
|
||||||
|
|
|
@ -7,6 +7,7 @@ use std::collections::VecDeque;
|
||||||
use std::env::current_exe;
|
use std::env::current_exe;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::fs::File;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Seek;
|
use std::io::Seek;
|
||||||
|
@ -106,16 +107,19 @@ pub struct Metadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_npm_vfs(root_dir_path: PathBuf) -> Result<FileBackedVfs, AnyError> {
|
pub fn load_npm_vfs(root_dir_path: PathBuf) -> Result<FileBackedVfs, AnyError> {
|
||||||
let file_path = current_exe().unwrap();
|
let data = libsui::find_section("d3n0l4nd").unwrap();
|
||||||
let mut file = std::fs::File::open(file_path)?;
|
|
||||||
file.seek(SeekFrom::End(-(TRAILER_SIZE as i64)))?;
|
// We do the first part sync so it can complete quickly
|
||||||
let mut trailer = [0; TRAILER_SIZE];
|
let trailer: [u8; TRAILER_SIZE] = data[0..TRAILER_SIZE].try_into().unwrap();
|
||||||
file.read_exact(&mut trailer)?;
|
let trailer = match Trailer::parse(&trailer)? {
|
||||||
let trailer = Trailer::parse(&trailer)?.unwrap();
|
None => panic!("Could not find trailer"),
|
||||||
file.seek(SeekFrom::Start(trailer.npm_vfs_pos))?;
|
Some(trailer) => trailer,
|
||||||
let mut vfs_data = vec![0; trailer.npm_vfs_len() as usize];
|
};
|
||||||
file.read_exact(&mut vfs_data)?;
|
let data = &data[TRAILER_SIZE..];
|
||||||
let mut dir: VirtualDirectory = serde_json::from_slice(&vfs_data)?;
|
|
||||||
|
let vfs_data =
|
||||||
|
&data[trailer.npm_vfs_pos as usize..trailer.npm_files_pos as usize];
|
||||||
|
let mut dir: VirtualDirectory = serde_json::from_slice(vfs_data)?;
|
||||||
|
|
||||||
// align the name of the directory with the root dir
|
// align the name of the directory with the root dir
|
||||||
dir.name = root_dir_path
|
dir.name = root_dir_path
|
||||||
|
@ -129,38 +133,32 @@ pub fn load_npm_vfs(root_dir_path: PathBuf) -> Result<FileBackedVfs, AnyError> {
|
||||||
root_path: root_dir_path,
|
root_path: root_dir_path,
|
||||||
start_file_offset: trailer.npm_files_pos,
|
start_file_offset: trailer.npm_files_pos,
|
||||||
};
|
};
|
||||||
Ok(FileBackedVfs::new(file, fs_root))
|
Ok(FileBackedVfs::new(data.to_vec(), fs_root))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_binary_bytes(
|
fn write_binary_bytes(
|
||||||
writer: &mut impl Write,
|
mut file_writer: File,
|
||||||
original_bin: Vec<u8>,
|
original_bin: Vec<u8>,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
eszip: eszip::EszipV2,
|
eszip: eszip::EszipV2,
|
||||||
npm_vfs: Option<&VirtualDirectory>,
|
npm_vfs: Option<&VirtualDirectory>,
|
||||||
npm_files: &Vec<Vec<u8>>,
|
npm_files: &Vec<Vec<u8>>,
|
||||||
|
compile_flags: &CompileFlags,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
let metadata = serde_json::to_string(metadata)?.as_bytes().to_vec();
|
let metadata = serde_json::to_string(metadata)?.as_bytes().to_vec();
|
||||||
let npm_vfs = serde_json::to_string(&npm_vfs)?.as_bytes().to_vec();
|
let npm_vfs = serde_json::to_string(&npm_vfs)?.as_bytes().to_vec();
|
||||||
let eszip_archive = eszip.into_bytes();
|
let eszip_archive = eszip.into_bytes();
|
||||||
|
|
||||||
writer.write_all(&original_bin)?;
|
let mut writer = Vec::new();
|
||||||
writer.write_all(&eszip_archive)?;
|
|
||||||
writer.write_all(&metadata)?;
|
|
||||||
writer.write_all(&npm_vfs)?;
|
|
||||||
for file in npm_files {
|
|
||||||
writer.write_all(file)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// write the trailer, which includes the positions
|
// write the trailer, which includes the positions
|
||||||
// of the data blocks in the file
|
// of the data blocks in the file
|
||||||
writer.write_all(&{
|
writer.write_all(&{
|
||||||
let eszip_pos = original_bin.len() as u64;
|
let metadata_pos = eszip_archive.len() as u64;
|
||||||
let metadata_pos = eszip_pos + (eszip_archive.len() as u64);
|
|
||||||
let npm_vfs_pos = metadata_pos + (metadata.len() as u64);
|
let npm_vfs_pos = metadata_pos + (metadata.len() as u64);
|
||||||
let npm_files_pos = npm_vfs_pos + (npm_vfs.len() as u64);
|
let npm_files_pos = npm_vfs_pos + (npm_vfs.len() as u64);
|
||||||
Trailer {
|
Trailer {
|
||||||
eszip_pos,
|
eszip_pos: 0,
|
||||||
metadata_pos,
|
metadata_pos,
|
||||||
npm_vfs_pos,
|
npm_vfs_pos,
|
||||||
npm_files_pos,
|
npm_files_pos,
|
||||||
|
@ -168,27 +166,36 @@ fn write_binary_bytes(
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
writer.write_all(&eszip_archive)?;
|
||||||
|
writer.write_all(&metadata)?;
|
||||||
|
writer.write_all(&npm_vfs)?;
|
||||||
|
for file in npm_files {
|
||||||
|
writer.write_all(file)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = compile_flags.resolve_target();
|
||||||
|
if target.contains("linux") {
|
||||||
|
libsui::Elf::new(&original_bin).append(&writer, &mut file_writer)?;
|
||||||
|
} else if target.contains("windows") {
|
||||||
|
libsui::PortableExecutable::from(&original_bin)?
|
||||||
|
.write_resource("d3n0l4nd", writer)?
|
||||||
|
.build(&mut file_writer)?;
|
||||||
|
} else if target.contains("darwin") {
|
||||||
|
libsui::Macho::from(original_bin)?
|
||||||
|
.write_section("d3n0l4nd", writer)?
|
||||||
|
.build(&mut file_writer)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_standalone_binary(exe_path: &Path) -> bool {
|
pub fn is_standalone_binary(exe_path: &Path) -> bool {
|
||||||
let Ok(mut output_file) = std::fs::File::open(exe_path) else {
|
let Ok(data) = std::fs::read(exe_path) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if output_file
|
|
||||||
.seek(SeekFrom::End(-(TRAILER_SIZE as i64)))
|
libsui::utils::is_elf(&data)
|
||||||
.is_err()
|
| libsui::utils::is_pe(&data)
|
||||||
{
|
| libsui::utils::is_macho(&data)
|
||||||
// This seek may fail because the file is too small to possibly be
|
|
||||||
// `deno compile` output.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let mut trailer = [0; TRAILER_SIZE];
|
|
||||||
if output_file.read_exact(&mut trailer).is_err() {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let (magic_trailer, _) = trailer.split_at(8);
|
|
||||||
magic_trailer == MAGIC_TRAILER
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function will try to run this binary as a standalone binary
|
/// This function will try to run this binary as a standalone binary
|
||||||
|
@ -197,40 +204,32 @@ pub fn is_standalone_binary(exe_path: &Path) -> bool {
|
||||||
/// then checking for the magic trailer string `d3n0l4nd`. If found,
|
/// then checking for the magic trailer string `d3n0l4nd`. If found,
|
||||||
/// the bundle is executed. If not, this function exits with `Ok(None)`.
|
/// the bundle is executed. If not, this function exits with `Ok(None)`.
|
||||||
pub fn extract_standalone(
|
pub fn extract_standalone(
|
||||||
exe_path: &Path,
|
|
||||||
cli_args: Cow<Vec<OsString>>,
|
cli_args: Cow<Vec<OsString>>,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
Option<impl Future<Output = Result<(Metadata, eszip::EszipV2), AnyError>>>,
|
Option<impl Future<Output = Result<(Metadata, eszip::EszipV2), AnyError>>>,
|
||||||
AnyError,
|
AnyError,
|
||||||
> {
|
> {
|
||||||
|
let Some(data) = libsui::find_section("d3n0l4nd") else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
// We do the first part sync so it can complete quickly
|
// We do the first part sync so it can complete quickly
|
||||||
let mut file = std::fs::File::open(exe_path)?;
|
let trailer = match Trailer::parse(&data[0..TRAILER_SIZE])? {
|
||||||
file.seek(SeekFrom::End(-(TRAILER_SIZE as i64)))?;
|
|
||||||
let mut trailer = [0; TRAILER_SIZE];
|
|
||||||
file.read_exact(&mut trailer)?;
|
|
||||||
let trailer = match Trailer::parse(&trailer)? {
|
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
Some(trailer) => trailer,
|
Some(trailer) => trailer,
|
||||||
};
|
};
|
||||||
|
|
||||||
file.seek(SeekFrom::Start(trailer.eszip_pos))?;
|
|
||||||
|
|
||||||
let cli_args = cli_args.into_owned();
|
let cli_args = cli_args.into_owned();
|
||||||
// If we have an eszip, read it out
|
// If we have an eszip, read it out
|
||||||
Ok(Some(async move {
|
Ok(Some(async move {
|
||||||
let bufreader =
|
let bufreader =
|
||||||
deno_core::futures::io::BufReader::new(AllowStdIo::new(file));
|
deno_core::futures::io::BufReader::new(&data[TRAILER_SIZE..]);
|
||||||
|
|
||||||
let (eszip, loader) = eszip::EszipV2::parse(bufreader)
|
let (eszip, loader) = eszip::EszipV2::parse(bufreader)
|
||||||
.await
|
.await
|
||||||
.context("Failed to parse eszip header")?;
|
.context("Failed to parse eszip header")?;
|
||||||
|
|
||||||
let mut bufreader =
|
let bufreader = loader.await.context("Failed to parse eszip archive")?;
|
||||||
loader.await.context("Failed to parse eszip archive")?;
|
|
||||||
|
|
||||||
bufreader
|
|
||||||
.seek(SeekFrom::Start(trailer.metadata_pos))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut metadata = String::new();
|
let mut metadata = String::new();
|
||||||
|
|
||||||
|
@ -405,7 +404,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
|
|
||||||
pub async fn write_bin(
|
pub async fn write_bin(
|
||||||
&self,
|
&self,
|
||||||
writer: &mut impl Write,
|
writer: File,
|
||||||
eszip: eszip::EszipV2,
|
eszip: eszip::EszipV2,
|
||||||
root_dir_url: EszipRelativeFileBaseUrl<'_>,
|
root_dir_url: EszipRelativeFileBaseUrl<'_>,
|
||||||
entrypoint: &ModuleSpecifier,
|
entrypoint: &ModuleSpecifier,
|
||||||
|
@ -518,7 +517,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn write_standalone_binary(
|
fn write_standalone_binary(
|
||||||
&self,
|
&self,
|
||||||
writer: &mut impl Write,
|
writer: File,
|
||||||
original_bin: Vec<u8>,
|
original_bin: Vec<u8>,
|
||||||
mut eszip: eszip::EszipV2,
|
mut eszip: eszip::EszipV2,
|
||||||
root_dir_url: EszipRelativeFileBaseUrl<'_>,
|
root_dir_url: EszipRelativeFileBaseUrl<'_>,
|
||||||
|
@ -654,6 +653,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
eszip,
|
eszip,
|
||||||
npm_vfs.as_ref(),
|
npm_vfs.as_ref(),
|
||||||
&npm_files,
|
&npm_files,
|
||||||
|
compile_flags,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -748,12 +748,12 @@ impl deno_io::fs::File for FileBackedVfsFile {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FileBackedVfs {
|
pub struct FileBackedVfs {
|
||||||
file: Mutex<File>,
|
file: Mutex<Vec<u8>>,
|
||||||
fs_root: VfsRoot,
|
fs_root: VfsRoot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileBackedVfs {
|
impl FileBackedVfs {
|
||||||
pub fn new(file: File, fs_root: VfsRoot) -> Self {
|
pub fn new(file: Vec<u8>, fs_root: VfsRoot) -> Self {
|
||||||
Self {
|
Self {
|
||||||
file: Mutex::new(file),
|
file: Mutex::new(file),
|
||||||
fs_root,
|
fs_root,
|
||||||
|
@ -836,11 +836,18 @@ impl FileBackedVfs {
|
||||||
pos: u64,
|
pos: u64,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
) -> std::io::Result<usize> {
|
) -> std::io::Result<usize> {
|
||||||
let mut fs_file = self.file.lock();
|
let data = self.file.lock();
|
||||||
fs_file.seek(SeekFrom::Start(
|
let start = self.fs_root.start_file_offset + file.offset + pos;
|
||||||
self.fs_root.start_file_offset + file.offset + pos,
|
let end = start + buf.len() as u64;
|
||||||
))?;
|
if end > data.len() as u64 {
|
||||||
fs_file.read(buf)
|
return Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::UnexpectedEof,
|
||||||
|
"unexpected EOF",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.copy_from_slice(&data[start as usize..end as usize]);
|
||||||
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dir_entry(&self, path: &Path) -> std::io::Result<&VirtualDirectory> {
|
pub fn dir_entry(&self, path: &Path) -> std::io::Result<&VirtualDirectory> {
|
||||||
|
@ -1016,12 +1023,12 @@ mod test {
|
||||||
file.write_all(file_data).unwrap();
|
file.write_all(file_data).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let file = std::fs::File::open(&virtual_fs_file).unwrap();
|
|
||||||
let dest_path = temp_dir.path().join("dest");
|
let dest_path = temp_dir.path().join("dest");
|
||||||
|
let data = std::fs::read(&virtual_fs_file).unwrap();
|
||||||
(
|
(
|
||||||
dest_path.to_path_buf(),
|
dest_path.to_path_buf(),
|
||||||
FileBackedVfs::new(
|
FileBackedVfs::new(
|
||||||
file,
|
data,
|
||||||
VfsRoot {
|
VfsRoot {
|
||||||
dir: root_dir,
|
dir: root_dir,
|
||||||
root_path: dest_path.to_path_buf(),
|
root_path: dest_path.to_path_buf(),
|
||||||
|
|
|
@ -124,12 +124,13 @@ pub async fn compile(
|
||||||
));
|
));
|
||||||
let temp_path = output_path.with_file_name(temp_filename);
|
let temp_path = output_path.with_file_name(temp_filename);
|
||||||
|
|
||||||
let mut file = std::fs::File::create(&temp_path).with_context(|| {
|
let file = std::fs::File::create(&temp_path).with_context(|| {
|
||||||
format!("Opening temporary file '{}'", temp_path.display())
|
format!("Opening temporary file '{}'", temp_path.display())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let write_result = binary_writer
|
let write_result = binary_writer
|
||||||
.write_bin(
|
.write_bin(
|
||||||
&mut file,
|
file,
|
||||||
eszip,
|
eszip,
|
||||||
root_dir_url,
|
root_dir_url,
|
||||||
&module_specifier,
|
&module_specifier,
|
||||||
|
@ -140,7 +141,6 @@ pub async fn compile(
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!("Writing temporary file '{}'", temp_path.display())
|
format!("Writing temporary file '{}'", temp_path.display())
|
||||||
});
|
});
|
||||||
drop(file);
|
|
||||||
|
|
||||||
// set it as executable
|
// set it as executable
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|
Loading…
Reference in a new issue