Skip to content

Commit

Permalink
Enable unpack/repack of RPM containers (#15405)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Mitchell <mmitche@microsoft.com>
  • Loading branch information
NikolaMilosavljevic and mmitche authored Jan 15, 2025
1 parent d49f22d commit fcbe4fd
Show file tree
Hide file tree
Showing 15 changed files with 355 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
<PackageReference Include="Newtonsoft.Json" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Microsoft.DotNet.SignTool" />
<InternalsVisibleTo Include="Microsoft.DotNet.SignTool.Tests" />
</ItemGroup>

<ItemGroup>
<None Include="build/**/*.*"
Pack="true"
Expand Down
21 changes: 19 additions & 2 deletions src/Microsoft.DotNet.Build.Tasks.Installers/src/RpmBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ public static string GetRpmHeaderArchitecture(Architecture architecture)
};
}

public static string GetDotNetArchitectureFromRpmHeaderArchitecture(string rpmPackageArchitecture)
{
return rpmPackageArchitecture switch
{
"noarch" => "any",
"i386" => "x86",
"i486" => "x86",
"i586" => "x86",
"i686" => "x86",
"x86_64" => "x64",
"armv6hl" => "arm",
"armv7hl" => "arm",
"aarch64" => "arm64",
_ => rpmPackageArchitecture
};
}

private static short GetRpmOS(OSPlatform os)
{
// See /usr/lib/rpm/rpmrc for the canonical OS mapping
Expand Down Expand Up @@ -204,8 +221,8 @@ public RpmPackage Build()

foreach (var script in _scripts)
{
entries.Add(new((RpmHeaderTag)Enum.Parse(typeof(RpmHeaderTag), script.Key), RpmHeaderEntryType.String, "/bin/sh"));
entries.Add(new((RpmHeaderTag)Enum.Parse(typeof(RpmHeaderTag), $"{script.Key}prog"), RpmHeaderEntryType.String, script.Value));
entries.Add(new((RpmHeaderTag)Enum.Parse(typeof(RpmHeaderTag), script.Key), RpmHeaderEntryType.String, script.Value));
entries.Add(new((RpmHeaderTag)Enum.Parse(typeof(RpmHeaderTag), $"{script.Key}prog"), RpmHeaderEntryType.String, "/bin/sh"));
}

MemoryStream cpioArchive = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public enum RpmHeaderTag
Url = 1020,
OperatingSystem = 1021,
Architecture = 1022,
Prein = 1023,
Postin = 1024,
Preun = 1025,
Postun = 1026,
FileSizes = 1028,
FileModes = 1030,
Expand All @@ -55,7 +57,9 @@ public enum RpmHeaderTag
ChangelogTimestamp = 1080,
ChangelogName = 1081,
ChangelogText = 1082,
Preinprog = 1085,
Postinprog = 1086,
Preunprog = 1087,
Postunprog = 1088,
FileDevices = 1095,
FileInode = 1096,
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.DotNet.SignTool.Tests/FakeSignTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ public override bool VerifySignedDeb(TaskLoggingHelper log, string filePath)
return true;
}

public override bool VerifySignedRpm(TaskLoggingHelper log, string filePath)
{
return true;
}

public override bool VerifySignedPowerShellFile(string filePath)
{
return true;
Expand Down
Binary file not shown.
98 changes: 98 additions & 0 deletions src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Microsoft.Build.Utilities;
using Xunit;
using Xunit.Abstractions;
using Microsoft.DotNet.Build.Tasks.Installers;

namespace Microsoft.DotNet.SignTool.Tests
{
Expand All @@ -34,6 +35,7 @@ public class SignToolTests : IDisposable
{".psc1", new List<SignInfo>{ new SignInfo("PSCCertificate") } },
{".dylib", new List<SignInfo>{ new SignInfo("DylibCertificate") } },
{".deb", new List<SignInfo>{ new SignInfo("LinuxSign") } },
{".rpm", new List<SignInfo>{ new SignInfo("LinuxSign") } },
{".dll", new List<SignInfo>{ new SignInfo("Microsoft400") } }, // lgtm [cs/common-default-passwords] Safe, these are certificate names
{".exe", new List<SignInfo>{ new SignInfo("Microsoft400") } }, // lgtm [cs/common-default-passwords] Safe, these are certificate names
{".msi", new List<SignInfo>{ new SignInfo("Microsoft400") } }, // lgtm [cs/common-default-passwords] Safe, these are certificate names
Expand Down Expand Up @@ -447,6 +449,60 @@ private string ExtractArchiveFromDebPackage(string debianPackage, string archive
File.WriteAllBytes(archive, ((MemoryStream)content).ToArray());
return archive;
}

private void ValidateProducedRpmContent(
string rpmPackage,
(string, string)[] expectedFilesOriginalHashes,
string[] signableFiles,
string originalUncompressedPayloadChecksum)
{
string tempDir = Path.Combine(_tmpDir, "verification");
Directory.CreateDirectory(tempDir);

string layout = Path.Combine(tempDir, "layout");
Directory.CreateDirectory(layout);

ZipData.ExtractRpmPayloadContents(rpmPackage, layout);

// Checks:
// Expected files are present
// Signed files have hashes different than original
foreach ((string targetSystemFilePath, string originalHash) in expectedFilesOriginalHashes)
{
string layoutFilePath = Path.Combine(layout, targetSystemFilePath);
File.Exists(layoutFilePath).Should().BeTrue();

using MD5 md5 = MD5.Create(); // lgtm [cs/weak-crypto] Azure Storage specifies use of MD5
using FileStream fileStream = File.OpenRead(layoutFilePath);
string newHash = Convert.ToHexString(md5.ComputeHash(fileStream));

if (signableFiles.Contains(targetSystemFilePath))
{
newHash.Should().NotBe(originalHash);
}
else
{
newHash.Should().Be(originalHash);
}
}

// Checks:
// Header payload digest matches the hash of the payload
// Header payload digest is different than the hash of the original payload
IReadOnlyList<RpmHeader<RpmHeaderTag>.Entry> headerEntries = ZipData.GetRpmHeaderEntries(rpmPackage);
string uncompressedPayloadDigest = ((string[])headerEntries.FirstOrDefault(e => e.Tag == RpmHeaderTag.UncompressedPayloadDigest).Value)[0].ToString();
uncompressedPayloadDigest.Should().NotBe(originalUncompressedPayloadChecksum);

using var stream = File.Open(rpmPackage, FileMode.Open);
using RpmPackage package = RpmPackage.Read(stream);
package.ArchiveStream.Seek(0, SeekOrigin.Begin);
using (SHA256 sha256 = SHA256.Create())
{
byte[] hash = sha256.ComputeHash(package.ArchiveStream);
string checksum = Convert.ToHexString(hash).ToLower();
checksum.Should().Be(uncompressedPayloadDigest);
}
}
#endif
[Fact]
public void EmptySigningList()
Expand Down Expand Up @@ -1419,6 +1475,48 @@ public void CheckDebSigning()
ValidateProducedDebContent(Path.Combine(_tmpDir, "test.deb"), expectedFilesOriginalHashes, signableFiles, expectedControlFileContent);
}

[LinuxOnlyFact]
public void CheckRpmSigning()
{
// List of files to be considered for signing
var itemsToSign = new ITaskItem[]
{
new TaskItem(GetResourcePath("test.rpm"))
};

// Default signing information
var strongNameSignInfo = new Dictionary<string, List<SignInfo>>();

// Overriding information
var fileSignInfo = new Dictionary<ExplicitCertificateKey, string>();

ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[]
{
"File 'mscorlib.dll' TargetFramework='.NETCoreApp,Version=v10.0' Certificate='Microsoft400'",
"File 'test.rpm' Certificate='LinuxSign'"
});

ValidateGeneratedProject(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[]
{
$@"<FilesToSign Include=""{Uri.EscapeDataString(Path.Combine(_tmpDir, "ContainerSigning", "1", "./usr/local/bin/mscorlib.dll"))}"">
<Authenticode>Microsoft400</Authenticode>
</FilesToSign>",
$@"<FilesToSign Include=""{Uri.EscapeDataString(Path.Combine(_tmpDir, "test.rpm"))}"">
<Authenticode>LinuxSign</Authenticode>
</FilesToSign>"
});

var expectedFilesOriginalHashes = new (string, string)[]
{
("usr/local/bin/hello", "644981BBD6F4ED1B3CF68CD0F47981AA"),
("usr/local/bin/mscorlib.dll", "B80EEBA2B8616B7C37E49B004D69BBB7")
};
string[] signableFiles = ["usr/local/bin/mscorlib.dll"];
string originalUncompressedPayloadChecksum = "216c2a99006d2e14d28a40c0f14a63f6462f533e89789a6f294186e0a0aad3fd";

ValidateProducedRpmContent(Path.Combine(_tmpDir, "test.rpm"), expectedFilesOriginalHashes, signableFiles, originalUncompressedPayloadChecksum);
}

[Fact]
public void VerifyDebIntegrity()
{
Expand Down
30 changes: 30 additions & 0 deletions src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,17 @@ private void VerifyCertificates(TaskLoggingHelper log)
log.LogError($"Deb package {fileName} must be signed with a LinuxSign certificate.");
}
}
else if (fileName.IsRpm())
{
if (isInvalidEmptyCertificate)
{
log.LogError($"Rpm package {fileName} should have a certificate name.");
}
if (!IsLinuxSignCertificate(fileName.SignInfo.Certificate))
{
log.LogError($"Rpm package {fileName} must be signed with a LinuxSign certificate.");
}
}
else if (fileName.IsNupkg())
{
if(isInvalidEmptyCertificate)
Expand Down Expand Up @@ -577,8 +588,27 @@ private void VerifyAfterSign(TaskLoggingHelper log, FileSignInfo file)
{
_log.LogError($"Deb package {file.FullPath} is not signed properly.");
}
else
{
_log.LogMessage(MessageImportance.Low, $"Deb package {file.FullPath} is signed properly");
}
#endif
}
else if (file.IsRpm())
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
_log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {file.FullPath} on non-Linux platform.");
}
else if (!_signTool.VerifySignedRpm(log, file.FullPath))
{
_log.LogError($"Rpm package {file.FullPath} is not signed properly.");
}
else
{
_log.LogMessage(MessageImportance.Low, $"Rpm package {file.FullPath} is signed properly");
}
}
else if (file.IsPowerShellScript())
{
if (!_signTool.VerifySignedPowerShellFile(file.FullPath))
Expand Down
14 changes: 13 additions & 1 deletion src/Microsoft.DotNet.SignTool/src/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,19 @@ private FileSignInfo ExtractSignInfo(
_log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is signed.");
}
}
else if(FileSignInfo.IsPowerShellScript(file.FullPath))
else if (FileSignInfo.IsRpm(file.FullPath))
{
isAlreadyAuthenticodeSigned = VerifySignatures.VerifySignedRpm(_log, file.FullPath);
if (!isAlreadyAuthenticodeSigned)
{
_log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is not signed.");
}
else
{
_log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is signed.");
}
}
else if (FileSignInfo.IsPowerShellScript(file.FullPath))
{
isAlreadyAuthenticodeSigned = VerifySignatures.VerifySignedPowerShellFile(file.FullPath);
if (!isAlreadyAuthenticodeSigned)
Expand Down
7 changes: 6 additions & 1 deletion src/Microsoft.DotNet.SignTool/src/FileSignInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ internal readonly struct FileSignInfo
internal static bool IsDeb(string path)
=> Path.GetExtension(path) == ".deb";

internal static bool IsRpm(string path)
=> Path.GetExtension(path) == ".rpm";

internal static bool IsPEFile(string path)
=> Path.GetExtension(path) == ".exe" || Path.GetExtension(path) == ".dll";

Expand Down Expand Up @@ -60,10 +63,12 @@ internal static bool IsPackage(string path)
=> IsVsix(path) || IsNupkg(path);

internal static bool IsZipContainer(string path)
=> IsPackage(path) || IsMPack(path) || IsZip(path) || IsTarGZip(path) || IsDeb(path);
=> IsPackage(path) || IsMPack(path) || IsZip(path) || IsTarGZip(path) || IsDeb(path) || IsRpm(path);

internal bool IsDeb() => IsDeb(FileName);

internal bool IsRpm() => IsRpm(FileName);

internal bool IsPEFile() => IsPEFile(FileName);

internal bool IsManaged() => ContentUtil.IsManaged(FullPath);
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.DotNet.SignTool/src/RealSignTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ public override bool VerifySignedDeb(TaskLoggingHelper log, string filePath)
return VerifySignatures.VerifySignedDeb(log, filePath);
}

public override bool VerifySignedRpm(TaskLoggingHelper log, string filePath)
{
return VerifySignatures.VerifySignedRpm(log, filePath);
}

public override bool VerifySignedPowerShellFile(string filePath)
{
return VerifySignatures.VerifySignedPowerShellFile(filePath);
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.DotNet.SignTool/src/SignTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ internal SignTool(SignToolArgs args, TaskLoggingHelper log)
public abstract bool LocalStrongNameSign(IBuildEngine buildEngine, int round, IEnumerable<FileSignInfo> files);

public abstract bool VerifySignedDeb(TaskLoggingHelper log, string filePath);
public abstract bool VerifySignedRpm(TaskLoggingHelper log, string filePath);
public abstract bool VerifySignedPEFile(Stream stream);
public abstract bool VerifySignedPowerShellFile(string filePath);
public abstract bool VerifySignedNugetFileMarker(string filePath);
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.DotNet.SignTool/src/SignToolConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal static class SignToolConstants
/// <summary>
/// List of known signable extensions. Copied, removing duplicates, from here:
/// https://microsoft.sharepoint.com/teams/prss/Codesign/SitePages/Signable%20Files.aspx
/// ".deb" is not in the list linked above, but it is a known signable extension.
/// ".deb" and ".rpm" are not in the list linked above, but they are known signable extension.
/// </summary>
public static readonly HashSet<string> SignableExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
Expand Down Expand Up @@ -113,6 +113,7 @@ internal static class SignToolConstants
".pyd",

".deb",
".rpm",
};

/// <summary>
Expand Down
3 changes: 3 additions & 0 deletions src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public override void RemoveStrongNameSign(string assemblyPath)
public override bool VerifySignedDeb(TaskLoggingHelper log, string filePath)
=> true;

public override bool VerifySignedRpm(TaskLoggingHelper log, string filePath)
=> true;

public override bool VerifySignedPEFile(Stream assemblyStream)
=> true;

Expand Down
7 changes: 7 additions & 0 deletions src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ internal static bool VerifySignedDeb(TaskLoggingHelper log, string filePath)
# endif
}

internal static bool VerifySignedRpm(TaskLoggingHelper log, string filePath)
{
// RPM signature verification is not yet implemented
log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {filePath} - not yet implemented.");
return true;
}

internal static bool VerifySignedPowerShellFile(string filePath)
{
return File.ReadLines(filePath).Any(line => line.IndexOf("# SIG # Begin Signature Block", StringComparison.OrdinalIgnoreCase) >= 0);
Expand Down
Loading

0 comments on commit fcbe4fd

Please sign in to comment.