-
Notifications
You must be signed in to change notification settings - Fork 384
Expand file tree
/
Copy pathPgpVerifier.cs
More file actions
124 lines (105 loc) · 5.4 KB
/
PgpVerifier.cs
File metadata and controls
124 lines (105 loc) · 5.4 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
// 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
{
private bool _supportsDetachedSignature;
protected PgpVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension, bool supportsDetachedSignature = false)
: base(log, exclusions, options, fileExtension)
{
_supportsDetachedSignature = supportsDetachedSignature;
}
public override SignatureVerificationResult VerifySignature(string path, string parent, string virtualPath)
{
if (_supportsDetachedSignature && File.Exists(path + ".sig"))
{
return VerifySupportedFileType(path, parent, virtualPath);
}
return VerifyUnsupportedFileType(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>
/// <param name="path"></param>
/// <param name="tempDir"></param>
/// <returns></returns>
protected virtual (string signatureDocument, string signableContent) GetSignatureDocumentAndSignableContent(string path, string tempDir)
{
if (_supportsDetachedSignature)
{
string signature = $"{path}.sig";
string signatureDocument = Path.Combine(tempDir, Path.GetFileName(signature));
File.Copy(signature, signatureDocument, overwrite: true);
return (signatureDocument, path);
}
throw new InvalidOperationException("GetSignatureDocumentAndSignableContent must be overridden for supported archive types that do not use detached signatures.");
}
protected override bool IsSigned(string path, SignatureVerificationResult svr)
{
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);
}
}
/// <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)
};
}
}
}