Closed dtivel closed 1 year ago
@dtivel, do you have positive and a negative (with and without) .cer files that I can see to understand this?
This is the part of the code that maps the X502Certificate2
to a POCO, which is later mapped to CSV and Kusto:
https://github.com/NuGet/Insights/blob/2e8f2926b669daf819328394a8cbfe48409b992a/src/Worker.Logic/CatalogScan/Drivers/PackageCertificateToCsv/CertificateRecord.cs#L35
I wonder if it's as simple as updating the code path there to check some more parts of the X502Certificate2
instance.
// This is an Extended Validation (EV) code signing certificate.
// EV code signing certificates are identified by the presence of a well-known certificate policy:
//
// extended-validation-codesigning (2.23.140.1.3)
//
// This policy is defined by the CA/Browser Forum at https://cabforum.org/object-registry/.
NiCertificates
| where FingerprintSHA256Hex == 'FB32E016FD317DB68C0B2B5B6E33231EE932B4B21E27F32B51654A483A10ADFB'
// This is not an EV code signing certificate.
NiCertificates
| where FingerprintSHA256Hex == '5A2901D6ADA3D18260B9C6DFE2133C95D74B9EEF6AE0E5DC334C8454D1477DF4'
Here's some sample C# code. (No promises on it compiling.)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0.
private static bool IsExtendedValidationCodeSigningCertificate(X509Certificate2 certificate)
{
var isEvCodeSigning = false;
X509Extension? certificatePolicy = certificate.Extensions[Oids.CertificatePolicies.Value!];
if (certificatePolicy is not null)
{
AsnReader reader = new(certificatePolicy.RawData, AsnEncodingRules.DER);
IReadOnlyList<PolicyInformation> policyInformations = ReadPolicies(reader.ReadSequence());
isEvCodeSigning = policyInformations.Any(
policy => policy.PolicyIdentifier.Value == Oids.ExtendedValidationCodeSigning.Value);
}
return isEvCodeSigning;
}
private static IReadOnlyList<PolicyInformation> ReadPolicies(AsnReader reader)
{
List<PolicyInformation> policies = new();
while (reader.HasData)
{
PolicyInformation policy = PolicyInformation.Read(reader);
policies.Add(policy);
}
return policies;
}
/*
From RFC 5280 (https://tools.ietf.org/html/rfc5280#appendix-A.2):
PolicyInformation ::= SEQUENCE {
policyIdentifier CertPolicyId,
policyQualifiers SEQUENCE SIZE (1..MAX) OF
PolicyQualifierInfo OPTIONAL }
CertPolicyId ::= OBJECT IDENTIFIER
*/
internal sealed class PolicyInformation
{
internal Oid PolicyIdentifier { get; }
internal IReadOnlyList<PolicyQualifierInfo> PolicyQualifiers { get; }
private PolicyInformation(Oid policyIdentifier, IReadOnlyList<PolicyQualifierInfo> policyQualifiers)
{
PolicyIdentifier = policyIdentifier;
PolicyQualifiers = policyQualifiers;
}
internal static PolicyInformation Read(AsnReader reader)
{
AsnReader policyInfoReader = reader.ReadSequence();
string policyIdentifier = policyInfoReader.ReadObjectIdentifier();
bool isAnyPolicy = policyIdentifier == Oids.AnyPolicy.Value;
IReadOnlyList<PolicyQualifierInfo>? policyQualifiers = null;
if (policyInfoReader.HasData)
{
policyQualifiers = ReadPolicyQualifiers(policyInfoReader, isAnyPolicy);
}
return new PolicyInformation(new Oid(policyIdentifier), policyQualifiers ?? Array.Empty<PolicyQualifierInfo>());
}
private static IReadOnlyList<PolicyQualifierInfo> ReadPolicyQualifiers(
AsnReader reader,
bool isAnyPolicy)
{
AsnReader policyQualifiersReader = reader.ReadSequence();
List<PolicyQualifierInfo> policyQualifiers = new();
while (policyQualifiersReader.HasData)
{
PolicyQualifierInfo policyQualifier = PolicyQualifierInfo.Read(policyQualifiersReader);
if (isAnyPolicy)
{
if (policyQualifier.PolicyQualifierId.Value != Oids.IdQtCps.Value &&
policyQualifier.PolicyQualifierId.Value != Oids.IdQtUnotice.Value)
{
throw new Exception("InvalidAsn1");
}
}
policyQualifiers.Add(policyQualifier);
}
if (policyQualifiers.Count == 0)
{
throw new Exception("InvalidAsn1");
}
return policyQualifiers;
}
}
/*
From RFC 5280 (https://tools.ietf.org/html/rfc5280#appendix-A.2):
PolicyQualifierInfo ::= SEQUENCE {
policyQualifierId PolicyQualifierId,
qualifier ANY DEFINED BY policyQualifierId }
-- policyQualifierIds for Internet policy qualifiers
id-qt OBJECT IDENTIFIER ::= { id-pkix 2 }
id-qt-cps OBJECT IDENTIFIER ::= { id-qt 1 }
id-qt-unotice OBJECT IDENTIFIER ::= { id-qt 2 }
PolicyQualifierId ::= OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice )
*/
internal sealed class PolicyQualifierInfo
{
internal Oid PolicyQualifierId { get; }
internal ReadOnlyMemory<byte> Qualifier { get; }
private PolicyQualifierInfo(Oid policyQualifierId, ReadOnlyMemory<byte> qualifier)
{
PolicyQualifierId = policyQualifierId;
Qualifier = qualifier;
}
internal static PolicyQualifierInfo Read(AsnReader reader)
{
AsnReader policyQualifierReader = reader.ReadSequence();
string policyQualifierId = policyQualifierReader.ReadObjectIdentifier();
ReadOnlyMemory<byte> qualifier = null;
if (policyQualifierReader.HasData)
{
qualifier = policyQualifierReader.ReadEncodedValue();
policyQualifierReader.ThrowIfNotEmpty();
}
return new PolicyQualifierInfo(new Oid(policyQualifierId), qualifier);
}
}
internal static class Oids
{
internal static readonly Oid AnyPolicy = new(DottedDecimals.AnyPolicy);
internal static readonly Oid CertificatePolicies = new(DottedDecimals.CertificatePolicies);
internal static readonly Oid ExtendedValidationCodeSigning = new(DottedDecimals.ExtendedValidationCodeSigning);
internal static readonly Oid IdQtCps = new(DottedDecimals.IdQtCps);
internal static readonly Oid IdQtUnotice = new(DottedDecimals.IdQtUnotice);
private class DottedDecimals
{
// RFC 5280 "anyPolicy" https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.4
internal const string AnyPolicy = "2.5.29.32.0";
// RFC 5280 "id-ce-certificatePolicies" https://www.rfc-editor.org/rfc/rfc5280.html#section-4.2.1.4
internal const string CertificatePolicies = "2.5.29.32";
// CA/B Forum "extended-validation-codesigning" https://cabforum.org/object-registry/
internal const string ExtendedValidationCodeSigning = "2.23.140.1.3";
// RFC 5280 "id-qt-cps" https://www.rfc-editor.org/rfc/rfc5280.html#section-4.2.1.4
internal const string IdQtCps = "1.3.6.1.5.5.7.2.1";
// RFC 5280 "id-qt-unotice" https://www.rfc-editor.org/rfc/rfc5280.html#section-4.2.1.4
internal const string IdQtUnotice = "1.3.6.1.5.5.7.2.2";
}
}
Done with f869511119c335eb7f02d4265e24205e91f3b589
See https://en.wikipedia.org/wiki/Extended_Validation_Certificate.
I don't think it's important to know if a certificate is domain validation (DV) or organization validation (OV).