File: Verification\DebVerifier.cs
Web Access
Project: src\src\SignCheck\Microsoft.SignCheck\Microsoft.DotNet.SignCheckLibrary.csproj (Microsoft.DotNet.SignCheckLibrary)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.DotNet.Build.Tasks.Installers;
using Microsoft.SignCheck.Logging;
 
namespace Microsoft.SignCheck.Verification
{
    public class DebVerifier : LinuxPackageVerifier
    {
        public DebVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options) : base(log, exclusions, options, ".deb") { }
 
        protected override IEnumerable<ArchiveEntry> ReadArchiveEntries(string archivePath)
            => ReadDebContainerEntries(archivePath, "data.tar");
 
        protected override (string signatureDocument, string signableContent) GetSignatureDocumentAndSignableContent(string archivePath, string tempDir)
        {
            string signatureDocument = null;
            string signableContent = null;
            try
            {
                string debianBinary = ExtractDebContainerEntry(archivePath, "debian-binary", tempDir);
                string controlTar = ExtractDebContainerEntry(archivePath, "control.tar", tempDir);
                string dataTar = ExtractDebContainerEntry(archivePath, "data.tar", tempDir);
                signatureDocument = ExtractDebContainerEntry(archivePath, "_gpgorigin", tempDir);
 
                signableContent = Path.Combine(tempDir, "signableContent");
                Utils.RunBashCommand($"cat {debianBinary} {controlTar} {dataTar} > {signableContent}");
            }
            catch (FileNotFoundException)
            {
                // The signature document may be missing if the package is not signed.
            }
 
            return (signatureDocument, signableContent);
        }
 
        protected override void WriteArchiveEntry(ArchiveEntry archiveEntry, string targetPath)
            => File.WriteAllBytes(targetPath, ((MemoryStream)archiveEntry.ContentStream).ToArray());
 
        /// <summary>
        /// Read the entries in the deb container.
        /// </summary>
        private IEnumerable<ArchiveEntry> ReadDebContainerEntries(string archivePath, string match = null)
        {
            using var archive = new ArReader(File.OpenRead(archivePath), leaveOpen: false);
 
            while (archive.GetNextEntry() is ArEntry entry)
            {
                string relativePath = entry.Name; // lgtm [cs/zipslip] Archive from trusted source
 
                // The relative path ocassionally ends with a '/', which is not a valid path given that the path is a file.
                // Remove the following workaround once https://github.com/dotnet/arcade/issues/15384 is resolved.
                if (relativePath.EndsWith("/"))
                {
                    relativePath = relativePath.TrimEnd('/');
                }
 
                if (match == null || relativePath.StartsWith(match))
                {
                    yield return new ArchiveEntry()
                    {
                        RelativePath = relativePath,
                        ContentStream = entry.DataStream,
                        ContentSize = entry.DataStream.Length
                    };
                }
            }
        }
 
        /// <summary>
        /// Extract a single entry from the deb container.
        /// </summary>
        private string ExtractDebContainerEntry(string archivePath, string entryName, string workingDir)
        {
            IEnumerable<ArchiveEntry> entries = ReadDebContainerEntries(archivePath, entryName);
            if (!entries.Any())
            {
                throw new FileNotFoundException($"The entry '{entryName}' was not found in the archive '{archivePath}'");
            }
            ArchiveEntry archiveEntry = entries.First();
            string entryPath = Path.Combine(workingDir, archiveEntry.RelativePath);
            WriteArchiveEntry(archiveEntry, entryPath);
 
            return entryPath;
        }
    }
}