-
Notifications
You must be signed in to change notification settings - Fork 678
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update verify command #1825
Update verify command #1825
Conversation
@PatoBeltran, |
@@ -5375,7 +5375,7 @@ nuget verify-Signatures .\*.nupkg</value> | |||
<comment>Please don't localize this string</comment> | |||
</data> | |||
<data name="VerifyCommandSignaturesDescription" xml:space="preserve"> | |||
<value>Specifies the type of verification to be done. Currently only -Signatures verification is supported.</value> | |||
<value>Specifies that a verification of the signature of the package(s) should be performed.</value> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be more clear? Specifies that package signature verification should be performed
</data> | ||
<data name="SignCommandOutputPath" xml:space="preserve"> | ||
<value>Signed package(s) output path: {0}</value> | ||
<comment>0 - output directory path</comment> | ||
</data> | ||
<data name="Error_CommandFailedWithException" xml:space="preserve"> | ||
<value>NuGet Command failed. {0} threw exception: {1}</value> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would avoid threw exception
here, just print the message from the exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is debug only then the rule is: don't localize debug strings (if we ask someone to send us diag output we shouldn't have to translate it, and diag output isn't really for the user)
var packagesToVerify = LocalFolderUtility.ResolvePackageFromPath(verifyArgs.PackagePath); | ||
LocalFolderUtility.EnsurePackageFileExists(verifyArgs.PackagePath, packagesToVerify); | ||
var packagesToVerify = LocalFolderUtility.ResolvePackageFromPath(verifyArgs.PackagePath); | ||
LocalFolderUtility.EnsurePackageFileExists(verifyArgs.PackagePath, packagesToVerify); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Glad to see these APIs being used here 🥇 🏆
catch (InvalidDataException e) | ||
{ | ||
verifyArgs.Logger.LogError(string.Format(CultureInfo.CurrentCulture, Strings.VerifyCommand_PackageIsNotValid, package)); | ||
verifyArgs.Logger.LogDebug(string.Format(CultureInfo.CurrentCulture, Strings.Error_CommandFailedWithException, nameof(VerifySignatureForPackageAsync), e.Message)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't localize this just log the full stack if it would be useful, also use ExceptionUtilities
if it makes sense here
SignedPackageArchiveUtility.VerifySignedZipIntegrity(reader, hashAlgorithm, expectedHash); | ||
if (!SignedPackageArchiveUtility.VerifySignedZipIntegrity(reader, hashAlgorithm, expectedHash)) | ||
{ | ||
throw new SignatureException(Strings.SignatureFailurePackageTampered); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Include the path if possible. This could come from checking if the stream is a FileStream and casting it to get the path. During a large restore with hundreds of packages it can be really confusing if there is a generic error message that doesn't specify the source.
{ | ||
return false; | ||
} | ||
|
||
for (var i = 0; i < byteSignature.Length; ++i) | ||
{ | ||
var @byte = stream.ReadByte(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just use b
for byte instead of an @
throw new Exception(Strings.ErrorByteSignatureTooBig); | ||
} | ||
|
||
while (stream.Position != 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious how perf is for this. It seems like this should use a buffer and check within the buffer instead of going byte by byte. This can be profiled later, it shouldn't be changed in this PR
metadata.IsZip64 = true; | ||
metadata.Zip64EndOfCentralDirectoryRecordPosition = reader.BaseStream.Position; | ||
} | ||
catch { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment for why this is ignored
certStringBuilder.AppendLine($"{indentation}{string.Format(Strings.CertUtilityCertificateSubjectName, cert.Subject)}"); | ||
certStringBuilder.AppendLine($"{indentation}{string.Format(Strings.CertUtilityCertificateHash, cert.Thumbprint)}"); | ||
certStringBuilder.AppendLine($"{indentation}{string.Format(Strings.CertUtilityCertificateIssuer, cert.IssuerName.Name)}"); | ||
certStringBuilder.AppendLine($"{indentation}{string.Format(Strings.CertUtilityCertificateValidity, cert.NotBefore, cert.NotAfter)}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String format needs to use CurrentCulture for user facing strings (this applies everywhere)
@@ -51,7 +51,7 @@ private static void X509Certificate2ToString(X509Certificate2 cert, StringBuilde | |||
/// </summary> | |||
/// <param name="certCollection">X509Certificate2Collection to be converted to string.</param> | |||
/// <returns>string representation of the X509Certificate2Collection.</returns> | |||
public static string X509Certificate2CollectionToString(X509Certificate2Collection certCollection) | |||
public static string X509Certificate2CollectionToString(X509Certificate2Collection certCollection, string indentation = "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
avoid default parameters like this (nuget coding guideline)
|
||
static class SignatureTypeMethods | ||
{ | ||
public static string GetString(this SignatureType type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does type.ToString() work? I believe there is a way to let .NET handle this
</data> | ||
<data name="SignCommandOutputPath" xml:space="preserve"> | ||
<value>Signed package(s) output path: {0}</value> | ||
<comment>0 - output directory path</comment> | ||
</data> | ||
<data name="Error_CommandFailedWithException" xml:space="preserve"> | ||
<value>NuGet Command failed. {0} threw exception: {1}</value> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lower case "command".
<value>Finished with {0} errors and {1} warnings.</value> | ||
</data> | ||
<data name="VerifyCommand_Success" xml:space="preserve"> | ||
<value>Successfully verified package.</value> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package(s)
if (verifyArgs.Type != VerifyArgs.VerificationType.Signatures) | ||
var executedSuccessfully = false; | ||
|
||
if (ShouldExecuteType(verifyArgs, VerificationType.Signatures)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would read more clearly if you removed Type
everywhere (e.g.: VerificationType
, ShouldExecuteType
, etc.). This could be simply ShouldExecuteVerification
.
|
||
var trustProviders = SignatureVerificationProviderFactory.GetSignatureVerificationProviders(); | ||
var verifier = new PackageSignatureVerifier(trustProviders, SignedPackageVerifierSettings.RequireSigned); | ||
var trustProviders = SignatureVerificationProviderFactory.GetSignatureVerificationProviders(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems this should be renamed toverificationProviders
. If verification includes a trust check, no problem.
} | ||
executedSuccessfully = errorCount == 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't need to be evaluated after each package verification and can instead be evaluated once near the return
statement below.
|
||
SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashAlgorithm, zipMetadata.Zip64EndOfCentralDirectoryLocatorPosition + 16); | ||
var relativeOffset = (uint)(reader.ReadUInt32() - metadata.SignatureFileEntryTotalSize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
relativeOffsetOfLocalFileHeader
@@ -51,7 +51,7 @@ private static void X509Certificate2ToString(X509Certificate2 cert, StringBuilde | |||
/// </summary> | |||
/// <param name="certCollection">X509Certificate2Collection to be converted to string.</param> | |||
/// <returns>string representation of the X509Certificate2Collection.</returns> | |||
public static string X509Certificate2CollectionToString(X509Certificate2Collection certCollection) | |||
public static string X509Certificate2CollectionToString(X509Certificate2Collection certCollection, string indentation = "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update XML doc comments.
public static Signature Load(SignedCms cms) | ||
{ | ||
var signerInfoCollection = cms.SignerInfos; | ||
if (signerInfoCollection.Count != 1) | ||
if (cms.SignerInfos.Count < 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the "root" signed CMS, the SignedData.signerInfos
count MUST be 1.
/// <summary> | ||
/// Create a signature based on a valid byte stream to be decoded as a signed cms | ||
/// </summary> | ||
/// <param name="stream">signature data</param> | ||
public static Signature Load(Stream stream) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a null
check on stream
.
{ | ||
return IsCertificateValid(certificate, out var chain, allowUntrustedRoot, checkRevocationStatus: true); | ||
} | ||
|
||
public static bool IsCertificatePublicKeyValid(X509Certificate2 certificate) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add XML doc comments.
Refactored code, added missing verification steps, added more detailed printing for verbose and debug mode, cleaned up strings
2d1b1b2
to
6bb36fe
Compare
6bb36fe
to
37f779d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀
3d99d6c
to
5236cec
Compare
5236cec
to
4539fcb
Compare
SignedPackageArchiveUtility.VerifySignedZipIntegrity(reader, hashAlgorithm, expectedHash); | ||
if (!SignedPackageArchiveUtility.VerifySignedZipIntegrity(reader, hashAlgorithm, expectedHash)) | ||
{ | ||
throw new SignatureException(Strings.SignatureFailurePackageTampered, GetIdentity()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Package integrity check failed." seems like a better message.
} | ||
|
||
// everything to UTC | ||
var timestampUpperGenTimeUtcTicks = tstInfoGenTime.AddTicks(tstInfoAccuracyInTicks).UtcTicks; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to convert into ticks all over the place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should using milliseconds be better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can compare DateTimeOffset
objects. tstInfo.Timestamp
is one.
var tstInfoGenTime = tstInfo.Timestamp;
var tstInfoAccuracy = tstInfo.AccuracyInMicroseconds;
long tstInfoAccuracyInSeconds;
if (tstInfoAccuracy.HasValue)
{
tstInfoAccuracyInSeconds = Math.Abs(tstInfoAccuracy.Value) / 1000000;
}
else if (string.Equals(tstInfo.PolicyId, Oids.BaselineTimestampPolicyOid))
{
tstInfoAccuracyInSeconds = 1;
}
else
{
tstInfoAccuracyInSeconds = 0;
}
// everything to UTC
var timestampUpperGenTimeUtc = tstInfoGenTime.AddSeconds(tstInfoAccuracyInSeconds);
var timestampLowerGenTimeUtc = tstInfoGenTime.AddSeconds(-tstInfoAccuracyInSeconds);
DateTimeOffset signerCertExpiryUtc = signerCertificate.NotAfter.ToUniversalTime();
DateTimeOffset signerCertBeginUtc = signerCertificate.NotBefore.ToUniversalTime();
return timestampUpperGenTimeUtcTicks < signerCertExpiryUtcTicks &&
timestampLowerGenTimeUtcTicks > signerCertBeginUtcTicks;
|
||
var result = SignatureVerificationStatus.Invalid; | ||
|
||
using (var chain = new X509Chain(useMachineContext: true)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be false
based on last discussion with you and Zhi. You can just use X509Chain
's default constructor which already passes false
.
</data> | ||
<data name="SignCommandOutputPath" xml:space="preserve"> | ||
<value>Signed package(s) output path: {0}</value> | ||
<comment>0 - output directory path</comment> | ||
</data> | ||
<data name="VerifyCommand_FinishedWithErrors" xml:space="preserve"> | ||
<value>Finished with {0} errors and {1} warnings.</value> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment indicating what the arguments to {0} and {1} are.
<value>Successfully verified package(s).</value> | ||
</data> | ||
<data name="VerifyCommand_VerifyingPackage" xml:space="preserve"> | ||
<value>Verifying {0}</value> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment indicating what an argument to {0} represents.
var errors = logMessages.Where(m => m.Level == LogLevel.Error).Count(); | ||
var warnings = logMessages.Where(m => m.Level == LogLevel.Warning).Count(); | ||
var errors = logMessages.Where(m => m.Level == LogLevel.Error).Count(); | ||
var warnings = logMessages.Where(m => m.Level == LogLevel.Warning).Count(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can replace Where(...)
with Count(...)
:
var errors = logMessages.Count(m => m.Level == LogLevel.Error);
var warnings = logMessages.Count(m => m.Level == LogLevel.Warning);
@@ -7,6 +7,7 @@ | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using NuGet.Common; | |||
using System.Diagnostics; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can undo?
@@ -60,7 +61,7 @@ public static string X509Certificate2CollectionToString(X509Certificate2Collecti | |||
for (var i = 0; i < Math.Min(_limit, certCollection.Count); i++) | |||
{ | |||
var cert = certCollection[i]; | |||
X509Certificate2ToString(cert, collectionStringBuilder); | |||
X509Certificate2ToString(cert, collectionStringBuilder, ""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indentation: ""
|
||
byte[] timestampByteArray; | ||
|
||
using (var timestampNativeCms = NativeCms.Decode(timestampCms.Encode(), detached: false)) | ||
using (var timestampCertChain = new X509Chain(useMachineContext: true)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the default constructor: new X509Chain()
.
var policy = timestampCertChain.ChainPolicy; | ||
|
||
policy.ApplicationPolicy.Add(new Oid(Oids.TimeStampingEkuOid)); | ||
policy.VerificationFlags = X509VerificationFlags.IgnoreNotTimeValid; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, but we should probably fail if:
DateTime.NowUtc < timestampSignerCertificate.NotBefore
The certificate's validity date would be in the future.
@dtivel addressed comments |
} | ||
|
||
// everything to UTC | ||
var timestampUpperGenTimeUtcTicks = tstInfoGenTime.AddTicks(tstInfoAccuracyInTicks).UtcTicks; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can compare DateTimeOffset
objects. tstInfo.Timestamp
is one.
var tstInfoGenTime = tstInfo.Timestamp;
var tstInfoAccuracy = tstInfo.AccuracyInMicroseconds;
long tstInfoAccuracyInSeconds;
if (tstInfoAccuracy.HasValue)
{
tstInfoAccuracyInSeconds = Math.Abs(tstInfoAccuracy.Value) / 1000000;
}
else if (string.Equals(tstInfo.PolicyId, Oids.BaselineTimestampPolicyOid))
{
tstInfoAccuracyInSeconds = 1;
}
else
{
tstInfoAccuracyInSeconds = 0;
}
// everything to UTC
var timestampUpperGenTimeUtc = tstInfoGenTime.AddSeconds(tstInfoAccuracyInSeconds);
var timestampLowerGenTimeUtc = tstInfoGenTime.AddSeconds(-tstInfoAccuracyInSeconds);
DateTimeOffset signerCertExpiryUtc = signerCertificate.NotAfter.ToUniversalTime();
DateTimeOffset signerCertBeginUtc = signerCertificate.NotBefore.ToUniversalTime();
return timestampUpperGenTimeUtcTicks < signerCertExpiryUtcTicks &&
timestampLowerGenTimeUtcTicks > signerCertBeginUtcTicks;
} | ||
|
||
// everything to UTC | ||
var timestampUpperGenTimeUtcTicks = tstInfoGenTime.AddMilliseconds(accuracyInMilliseconds); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove Ticks
from all these names.
@onovotny it seems that the data timestamped |
@onovotny glad to know! :) |
Fixes: NuGet/Home#6006
Fixes: NuGet/Home#6181
Fixes: NuGet/Home#6073
Fixes: NuGet/Home#6069
Refactored code, added missing verification steps, added more detailed
printing for verbose and debug mode, cleaned up strings
Also
Fixes: NuGet/Home#6212
Functional tests for verify flow are missing