Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 2 additions & 32 deletions crates/psign-digest-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3539,37 +3539,6 @@ fn artifact_signing_params_for_digest(
})
}

#[cfg(feature = "artifact-signing-rest")]
fn parse_artifact_signing_certificates(bytes: &[u8]) -> Result<(x509_cert::Certificate, Vec<x509_cert::Certificate>)> {
if let Ok(text) = std::str::from_utf8(bytes)
&& text.contains("-----BEGIN CERTIFICATE-----")
{
let mut certs = Vec::new();
let mut rest = text;
while let Some(start) = rest.find("-----BEGIN CERTIFICATE-----") {
rest = &rest[start..];
let Some(end) = rest.find("-----END CERTIFICATE-----") else {
return Err(anyhow!("unterminated PEM certificate in Artifact Signing signingCertificate"));
};
let end = end + "-----END CERTIFICATE-----".len();
certs.push(
rdp::parse_certificate(&rest.as_bytes()[..end])
.context("parse Artifact Signing PEM certificate")?,
);
rest = &rest[end..];
}
let mut iter = certs.into_iter();
let signer = iter
.next()
.ok_or_else(|| anyhow!("Artifact Signing signingCertificate did not contain a certificate"))?;
return Ok((signer, iter.collect()));
}
Ok((
rdp::parse_certificate(bytes).context("parse Artifact Signing DER signing certificate")?,
Vec::new(),
))
}

#[cfg(feature = "artifact-signing-rest")]
fn create_pe_authenticode_pkcs7_der_artifact_signing(
pe: &[u8],
Expand All @@ -3593,7 +3562,8 @@ fn create_pe_authenticode_pkcs7_der_artifact_signing(
eprintln!("[debug] {msg}");
}
})?;
let (signer_cert, mut chain) = parse_artifact_signing_certificates(&signed.signing_certificate)?;
let (signer_cert, mut chain) =
pkcs7::parse_artifact_signing_certificates(&signed.signing_certificate)?;
for chain_cert in chain_certs {
let bytes =
std::fs::read(&chain_cert).with_context(|| format!("read {}", chain_cert.display()))?;
Expand Down
190 changes: 141 additions & 49 deletions crates/psign-opc-sign/src/nuget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,72 @@ use zip::write::FileOptions;
pub const PACKAGE_SIGNATURE_FILE_NAME: &str = ".signature.p7s";
pub const SIGNATURE_CONTENT_VERSION: &str = "1";

fn nuget_zip_options(compression: zip::CompressionMethod) -> FileOptions {
FileOptions::default().compression_method(compression)
}

fn normalize_nuget_zip_metadata(bytes: &mut [u8]) -> Result<()> {
let eocd = bytes
.windows(4)
.rposition(|window| window == [0x50, 0x4b, 0x05, 0x06])
.ok_or_else(|| anyhow!("ZIP central directory end not found"))?;
if eocd + 22 > bytes.len() {
return Err(anyhow!("truncated ZIP central directory end"));
}

let central_dir_size = u32::from_le_bytes(
bytes[eocd + 12..eocd + 16]
.try_into()
.expect("central directory size slice"),
) as usize;
let central_dir_offset = u32::from_le_bytes(
bytes[eocd + 16..eocd + 20]
.try_into()
.expect("central directory offset slice"),
) as usize;
let central_dir_end = central_dir_offset
.checked_add(central_dir_size)
.ok_or_else(|| anyhow!("ZIP central directory size overflow"))?;
if central_dir_end > bytes.len() {
return Err(anyhow!("ZIP central directory extends past end of file"));
}

let mut pos = central_dir_offset;
while pos < central_dir_end {
if pos + 46 > bytes.len() || bytes[pos..pos + 4] != [0x50, 0x4b, 0x01, 0x02] {
return Err(anyhow!("invalid ZIP central directory entry"));
}
bytes[pos + 5] = 0;
bytes[pos + 38..pos + 42].fill(0);

let name_len = u16::from_le_bytes(
bytes[pos + 28..pos + 30]
.try_into()
.expect("file name length slice"),
) as usize;
let extra_len = u16::from_le_bytes(
bytes[pos + 30..pos + 32]
.try_into()
.expect("extra field length slice"),
) as usize;
let comment_len = u16::from_le_bytes(
bytes[pos + 32..pos + 34]
.try_into()
.expect("file comment length slice"),
) as usize;
pos = pos
.checked_add(46)
.and_then(|n| n.checked_add(name_len))
.and_then(|n| n.checked_add(extra_len))
.and_then(|n| n.checked_add(comment_len))
.ok_or_else(|| anyhow!("ZIP central directory entry size overflow"))?;
}
if pos != central_dir_end {
return Err(anyhow!("ZIP central directory entry length mismatch"));
}
Ok(())
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NuGetHashAlgorithm {
Sha256,
Expand Down Expand Up @@ -243,41 +309,47 @@ where

fn write_package_without_signature_impl<R, W>(
reader: R,
writer: W,
mut writer: W,
require_signature: bool,
) -> Result<()>
where
R: Read + Seek,
W: Write + Seek,
{
let mut input = zip::ZipArchive::new(reader).context("open NuGet ZIP")?;
let mut output = zip::ZipWriter::new(writer);
let mut out = std::io::Cursor::new(Vec::new());
let mut had_signature = false;

for i in 0..input.len() {
let mut file = input.by_index(i).context("read NuGet ZIP entry")?;
let name = normalize_zip_part_name(file.name())?;
if name == PACKAGE_SIGNATURE_FILE_NAME {
had_signature = true;
continue;
{
let mut output = zip::ZipWriter::new(&mut out);
for i in 0..input.len() {
let mut file = input.by_index(i).context("read NuGet ZIP entry")?;
let name = normalize_zip_part_name(file.name())?;
if name == PACKAGE_SIGNATURE_FILE_NAME {
had_signature = true;
continue;
}

let options = nuget_zip_options(file.compression());
if file.is_dir() {
output.add_directory(name, options)?;
} else {
output.start_file(name, options)?;
std::io::copy(&mut file, &mut output)?;
}
}

let options = FileOptions::default().compression_method(file.compression());
if file.is_dir() {
output.add_directory(name, options)?;
} else {
output.start_file(name, options)?;
std::io::copy(&mut file, &mut output)?;
if require_signature && !had_signature {
return Err(anyhow!(
"package does not contain {PACKAGE_SIGNATURE_FILE_NAME}"
));
}
}

if require_signature && !had_signature {
return Err(anyhow!(
"package does not contain {PACKAGE_SIGNATURE_FILE_NAME}"
));
output.finish()?;
}

output.finish()?;
let mut bytes = out.into_inner();
normalize_nuget_zip_metadata(&mut bytes)?;
writer.write_all(&bytes)?;
Ok(())
}

Expand Down Expand Up @@ -318,7 +390,7 @@ pub fn embed_signature_path(

pub fn embed_signature<R, W>(
reader: R,
writer: W,
mut writer: W,
signature_der: &[u8],
overwrite: bool,
) -> Result<()>
Expand All @@ -330,40 +402,47 @@ where
return Err(anyhow!("NuGet package signature payload is empty"));
}
let mut input = zip::ZipArchive::new(reader).context("open NuGet ZIP")?;
let mut output = zip::ZipWriter::new(writer);
let mut out = std::io::Cursor::new(Vec::new());
let mut had_signature = false;

for i in 0..input.len() {
let mut file = input.by_index(i).context("read NuGet ZIP entry")?;
let name = normalize_zip_part_name(file.name())?;
if name == PACKAGE_SIGNATURE_FILE_NAME {
had_signature = true;
if overwrite {
continue;
{
let mut output = zip::ZipWriter::new(&mut out);
for i in 0..input.len() {
let mut file = input.by_index(i).context("read NuGet ZIP entry")?;
let name = normalize_zip_part_name(file.name())?;
if name == PACKAGE_SIGNATURE_FILE_NAME {
had_signature = true;
if overwrite {
continue;
}
return Err(anyhow!(
"package already contains {}; pass overwrite to replace it",
PACKAGE_SIGNATURE_FILE_NAME
));
}

let options = nuget_zip_options(file.compression());
if file.is_dir() {
output.add_directory(name, options)?;
} else {
output.start_file(name, options)?;
std::io::copy(&mut file, &mut output)?;
}
return Err(anyhow!(
"package already contains {}; pass overwrite to replace it",
PACKAGE_SIGNATURE_FILE_NAME
));
}

let options = FileOptions::default().compression_method(file.compression());
if file.is_dir() {
output.add_directory(name, options)?;
} else {
output.start_file(name, options)?;
std::io::copy(&mut file, &mut output)?;
if !had_signature || overwrite {
output.start_file(
PACKAGE_SIGNATURE_FILE_NAME,
nuget_zip_options(zip::CompressionMethod::Stored),
)?;
output.write_all(signature_der)?;
}
}

if !had_signature || overwrite {
output.start_file(
PACKAGE_SIGNATURE_FILE_NAME,
FileOptions::default().compression_method(zip::CompressionMethod::Stored),
)?;
output.write_all(signature_der)?;
output.finish()?;
}
output.finish()?;
let mut bytes = out.into_inner();
normalize_nuget_zip_metadata(&mut bytes)?;
writer.write_all(&bytes)?;
Ok(())
}

Expand Down Expand Up @@ -476,7 +555,8 @@ mod tests {
let mut out = Cursor::new(Vec::new());

embed_signature(Cursor::new(zip), &mut out, b"cms", false).unwrap();
let info = inspect_package_reader_for_test(out.into_inner());
let signed = out.into_inner();
let info = inspect_package_reader_for_test(signed.clone());

assert_eq!(
info.entry(PACKAGE_SIGNATURE_FILE_NAME)
Expand All @@ -488,6 +568,18 @@ mod tests {
.map(|e| e.compression.as_str()),
Some("Stored")
);
let mut archive = zip::ZipArchive::new(Cursor::new(signed)).unwrap();
assert_eq!(
archive
.by_name(PACKAGE_SIGNATURE_FILE_NAME)
.unwrap()
.unix_mode(),
None
);
assert_eq!(
archive.by_name("lib/net8.0/a.dll").unwrap().unix_mode(),
None
);
}

#[test]
Expand Down
Loading