-
Notifications
You must be signed in to change notification settings - Fork 385
Expand file tree
/
Copy pathPgpVerifier.cs
More file actions
130 lines (111 loc) · 5.73 KB
/
PgpVerifier.cs
File metadata and controls
130 lines (111 loc) · 5.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// 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.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Microsoft.SignCheck.Logging;
namespace Microsoft.SignCheck.Verification
{
public abstract class PgpVerifier : ArchiveVerifier
{
protected PgpVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension)
: base(log, exclusions, options, fileExtension)
{
}
public override SignatureVerificationResult VerifySignature(string path, string parent, string virtualPath)
=> VerifySupportedFileType(path, parent, virtualPath);
/// <summary>
/// Returns the paths to the signature document and the signable content.
/// Used to verify the signature of the package using gpg.
/// </summary>
protected abstract (string signatureDocument, string signableContent) GetSignatureDocumentAndSignableContent(string path, string tempDir);
/// <summary>
/// Verifies the signature of a file using a detached .sig file.
/// If the .sig file exists, verifies as a supported file type; otherwise, as unsupported.
/// </summary>
protected SignatureVerificationResult VerifyDetachedSignature(string path, string parent, string virtualPath)
{
if (File.Exists(path + ".sig"))
{
return VerifySupportedFileType(path, parent, virtualPath);
}
return VerifyUnsupportedFileType(path, parent, virtualPath);
}
/// <summary>
/// Returns the paths to the detached signature document and the signable content.
/// For use by verifiers whose signatures are stored in a separate .sig file.
/// </summary>
protected static (string signatureDocument, string signableContent) GetDetachedSignatureDocumentAndSignableContent(string path, string tempDir)
{
string signature = $"{path}.sig";
string signatureDocument = Path.Combine(tempDir, Path.GetFileName(signature));
File.Copy(signature, signatureDocument, overwrite: true);
return (signatureDocument, path);
}
protected override bool IsSigned(string path, SignatureVerificationResult svr)
{
#if NET
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
throw new PlatformNotSupportedException("Pgp verification is not supported on Windows.");
}
string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempDir);
// https://microsoft.sharepoint.com/teams/prss/esrp/info/SitePages/Linux%20GPG%20Signing.aspx
try
{
Utils.DownloadAndConfigurePublicKeys(tempDir);
(string signatureDocument, string signableContent) = GetSignatureDocumentAndSignableContent(path, tempDir);
if (string.IsNullOrEmpty(signatureDocument) || string.IsNullOrEmpty(signableContent))
{
return false;
}
(int exitCode, string output, string error) = Utils.RunBashCommand($"gpg --verify --status-fd 1 {signatureDocument} {signableContent}");
string verificationOutput = output + error;
if (!verificationOutput.Contains("Good signature"))
{
if (exitCode != 0 && !verificationOutput.Contains("no signature found"))
{
// Log an error if something other than a missing
// signature caused the verification to fail
svr.AddDetail(DetailKeys.Error, error);
}
return false;
}
Timestamp ts = GetTimestamp(verificationOutput);
ts.AddToSignatureVerificationResult(svr);
return ts.IsValid;
}
finally
{
Directory.Delete(tempDir, true);
}
#else
throw new PlatformNotSupportedException("Pgp verification is not supported on this platform.");
#endif
}
/// <summary>
/// Get the timestamp of the signature in the package.
/// </summary>
private Timestamp GetTimestamp(string verificationOutput)
{
Regex signatureTimestampsRegex = new Regex(@"VALIDSIG .+ \d+-\d+-\d+ (?<signedOn>\d+) (?<expiresOn>\d+) ");
Match signatureTimestampsMatch = signatureTimestampsRegex.Match(verificationOutput);
Regex signatureKeyInfoRegex = new Regex(@"using (?<algorithm>.+) key (?<keyId>.+)");
Match signatureKeyInfoMatch = signatureKeyInfoRegex.Match(verificationOutput);
string keyId = signatureKeyInfoMatch.GroupValueOrDefault("keyId");
(_, string keyInfo, _) = Utils.RunBashCommand($"gpg --list-keys --with-colons {keyId} | grep '^pub:'");
Regex keyInfoRegex = new Regex(@$"pub.+{keyId}:(?<createdOn>\d+):");
Match keyInfoMatch = keyInfoRegex.Match(keyInfo);
return new Timestamp()
{
SignedOn = signatureTimestampsMatch.GroupValueOrDefault("signedOn").DateTimeOrDefault(DateTime.MaxValue),
ExpiryDate = signatureTimestampsMatch.GroupValueOrDefault("expiresOn").DateTimeOrDefault(DateTime.MaxValue),
SignatureAlgorithm = signatureKeyInfoMatch.GroupValueOrDefault("algorithm"),
EffectiveDate = keyInfoMatch.GroupValueOrDefault("createdOn").DateTimeOrDefault(DateTime.MaxValue)
};
}
}
}