-
Notifications
You must be signed in to change notification settings - Fork 361
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
aecf645
commit e266d5f
Showing
4 changed files
with
237 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
193 changes: 193 additions & 0 deletions
193
src/SignCheck/Microsoft.SignCheck/Verification/PkgVerifier.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.IO; | ||
using System.Diagnostics; | ||
using System.Collections.Generic; | ||
using Microsoft.SignCheck.Logging; | ||
using System.Security.Cryptography; | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
using System.Text.RegularExpressions; | ||
using System.Linq; | ||
using Microsoft.DotNet.MacOsPkg.Core; | ||
|
||
namespace Microsoft.SignCheck.Verification | ||
{ | ||
public class PkgVerifier : ArchiveVerifier | ||
{ | ||
public PkgVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension) : base(log, exclusions, options, fileExtension) | ||
{ | ||
if (fileExtension != ".pkg" && fileExtension != ".app") | ||
{ | ||
throw new ArgumentException("PkgVerifier can only be used with .pkg and .app files."); | ||
} | ||
} | ||
|
||
public override SignatureVerificationResult VerifySignature(string path, string parent, string virtualPath) | ||
{ | ||
SignatureVerificationResult svr = new SignatureVerificationResult(path, parent, virtualPath); | ||
string fullPath = svr.FullPath; | ||
|
||
try | ||
{ | ||
svr.IsSigned = IsSigned(fullPath, svr); | ||
svr.AddDetail(DetailKeys.File, SignCheckResources.DetailSigned, svr.IsSigned); | ||
} | ||
catch (PlatformNotSupportedException) | ||
{ | ||
// Log the error and return an unsupported file type result | ||
// because processing pkgs and apps is not supported on non-OSX platforms | ||
svr = SignatureVerificationResult.UnsupportedFileTypeResult(path, parent, virtualPath); | ||
svr.AddDetail(DetailKeys.File, SignCheckResources.DetailSigned, SignCheckResources.NA); | ||
} | ||
|
||
VerifyContent(svr); | ||
|
||
return svr; | ||
} | ||
|
||
protected override IEnumerable<ArchiveEntry> ReadArchiveEntries(string archivePath) | ||
{ | ||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | ||
{ | ||
throw new PlatformNotSupportedException("The MacOsPkg tooling is only supported on macOS."); | ||
} | ||
|
||
string extractionPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); | ||
try | ||
{ | ||
if (MacOsPkgCore.Unpack(archivePath, extractionPath) != 0) | ||
{ | ||
throw new Exception($"Failed to unpack pkg '{archivePath}'"); | ||
} | ||
|
||
foreach (var path in Directory.EnumerateFiles(extractionPath, "*.*", SearchOption.AllDirectories)) | ||
{ | ||
var relativePath = path.Substring(extractionPath.Length + 1).Replace(Path.DirectorySeparatorChar, '/'); | ||
using var stream = (Stream)File.Open(path, FileMode.Open); | ||
yield return new ArchiveEntry() | ||
{ | ||
RelativePath = relativePath, | ||
ContentStream = stream, | ||
ContentSize = stream?.Length ?? 0 | ||
}; | ||
} | ||
} | ||
finally | ||
{ | ||
// Cleanup the extraction path if it was created by the Unpack method | ||
if (Directory.Exists(extractionPath)) | ||
{ | ||
Directory.Delete(extractionPath, true); | ||
} | ||
} | ||
} | ||
|
||
private bool IsSigned(string path, SignatureVerificationResult svr) | ||
{ | ||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | ||
{ | ||
throw new PlatformNotSupportedException("The MacOsPkg tooling is only supported on macOS."); | ||
} | ||
|
||
// macOsPkgCore.VerifySignature writes the output to the console. | ||
// We need to capture the output and parse it for timestamps. | ||
var consoleOutput = Console.Out; | ||
StringWriter outputWriter = new StringWriter(); | ||
Console.SetOut(outputWriter); | ||
|
||
var errorOutput = Console.Error; | ||
StringWriter errorOutputWriter = new StringWriter(); | ||
Console.SetError(errorOutputWriter); | ||
|
||
try | ||
{ | ||
// Verify the signature | ||
if (MacOsPkgCore.VerifySignature(path) != 0) | ||
{ | ||
string errorMessage = errorOutputWriter.ToString(); | ||
// Ignore the pkgutil --check-signature error message, it is expected if the file is not signed | ||
if (!string.IsNullOrEmpty(errorMessage) && !errorMessage.Contains("pkgutil --check-signature")) | ||
{ | ||
svr.AddDetail(DetailKeys.Error, errorMessage); | ||
} | ||
return false; | ||
} | ||
|
||
// Verify the timestamps | ||
IEnumerable<Timestamp> timestamps = GetTimestamps(outputWriter.ToString()); | ||
if (!timestamps.Any()) | ||
{ | ||
svr.AddDetail(DetailKeys.Error, SignCheckResources.ErrorInvalidOrMissingTimestamp); | ||
return false; | ||
} | ||
|
||
foreach (Timestamp ts in timestamps) | ||
{ | ||
if (ts.IsValid) | ||
{ | ||
svr.AddDetail(DetailKeys.Misc, SignCheckResources.DetailTimestamp, ts.SignedOn, ts.SignatureAlgorithm); | ||
} | ||
else | ||
{ | ||
if (ts.SignedOn == DateTime.MaxValue || ts.ExpiryDate == DateTime.MinValue) | ||
{ | ||
svr.AddDetail(DetailKeys.Error, SignCheckResources.ErrorInvalidOrMissingTimestamp); | ||
} | ||
else | ||
{ | ||
svr.AddDetail(DetailKeys.Error, SignCheckResources.DetailTimestampOutisdeCertValidity, ts.SignedOn, ts.EffectiveDate, ts.ExpiryDate); | ||
} | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
finally | ||
{ | ||
Console.SetOut(consoleOutput); | ||
Console.SetError(errorOutput); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Get the timestamps from the output of the pkgutil command. | ||
/// Assumes that the verify command has already been run. | ||
/// </summary> | ||
private IEnumerable<Timestamp> GetTimestamps(string signingVerificationOutput) | ||
{ | ||
string timestampRegex = @"(?<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} \+\d{4})"; | ||
|
||
Regex signedOnRegex = new Regex(@"Signed with a trusted timestamp on: " + timestampRegex); | ||
string signedOnString = GetRegexValue(signedOnRegex.Match(signingVerificationOutput), "timestamp"); | ||
if(!DateTime.TryParse(signedOnString, out DateTime signedOnTimestamp)) | ||
{ | ||
signedOnTimestamp = DateTime.MaxValue; | ||
} | ||
|
||
Regex certificateChainRegex = new Regex(@"Expires: " + timestampRegex + "\n (?<algorithm>.+) Fingerprint:"); | ||
IEnumerable<Match> matches = certificateChainRegex.Matches(signingVerificationOutput).ToList(); | ||
|
||
return matches.Select(match => | ||
{ | ||
string certificateString = GetRegexValue(match, "timestamp"); | ||
if (!DateTime.TryParse(certificateString, out DateTime certificateTimestamp)) | ||
{ | ||
certificateTimestamp = DateTime.MinValue; | ||
} | ||
return new Timestamp() | ||
{ | ||
EffectiveDate = signedOnTimestamp, | ||
ExpiryDate = certificateTimestamp, | ||
SignedOn = signedOnTimestamp, | ||
SignatureAlgorithm = GetRegexValue(match, "algorithm") | ||
}; | ||
}); | ||
} | ||
|
||
private string GetRegexValue(Match match, string groupName) => | ||
match.Success ? match.Groups[groupName].Value : null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters