55using System . Collections . Generic ;
66using System . Data . Entity ;
77using System . Linq ;
8+ using System . Security . Cryptography . X509Certificates ;
89using System . Threading ;
910using System . Threading . Tasks ;
1011using Microsoft . Extensions . Logging ;
1112using Microsoft . Extensions . Options ;
1213using NuGet . Jobs . Validation . PackageSigning . Storage ;
1314using NuGet . Packaging . Signing ;
1415using NuGet . Services . Validation ;
16+ using NuGetGallery ;
1517
1618namespace NuGet . Jobs . Validation . PackageSigning . ProcessSignature
1719{
1820 public class SignaturePartsExtractor : ISignaturePartsExtractor
1921 {
2022 private readonly ICertificateStore _certificateStore ;
21- private readonly IValidationEntitiesContext _entitiesContext ;
23+ private readonly IValidationEntitiesContext _validationEntitiesContext ;
24+ private readonly IEntitiesContext _galleryEntitiesContext ;
2225 private readonly IOptionsSnapshot < ProcessSignatureConfiguration > _configuration ;
2326 private readonly ILogger < SignaturePartsExtractor > _logger ;
2427
2528 public SignaturePartsExtractor (
2629 ICertificateStore certificateStore ,
27- IValidationEntitiesContext entitiesContext ,
30+ IValidationEntitiesContext validationEntitiesContext ,
31+ IEntitiesContext galleryEntitiesContext ,
2832 IOptionsSnapshot < ProcessSignatureConfiguration > configuration ,
2933 ILogger < SignaturePartsExtractor > logger )
3034 {
3135 _certificateStore = certificateStore ?? throw new ArgumentNullException ( nameof ( certificateStore ) ) ;
32- _entitiesContext = entitiesContext ?? throw new ArgumentNullException ( nameof ( entitiesContext ) ) ;
36+ _validationEntitiesContext = validationEntitiesContext ?? throw new ArgumentNullException ( nameof ( validationEntitiesContext ) ) ;
37+ _galleryEntitiesContext = galleryEntitiesContext ?? throw new ArgumentNullException ( nameof ( galleryEntitiesContext ) ) ;
3338 _configuration = configuration ?? throw new ArgumentNullException ( nameof ( configuration ) ) ;
3439 _logger = logger ?? throw new ArgumentNullException ( nameof ( logger ) ) ;
3540 }
@@ -53,8 +58,80 @@ public async Task ExtractAsync(int packageKey, PrimarySignature primarySignature
5358 await SaveCertificatesToStoreAsync ( context ) ;
5459
5560 // Commit the database changes.
56- await _entitiesContext . SaveChangesAsync ( ) ;
61+ await _validationEntitiesContext . SaveChangesAsync ( ) ;
62+
63+ // Update certificate information in the gallery.
64+ await UpdateCertificateInformationAsync ( context ) ;
65+ }
66+ }
67+
68+ private async Task UpdateCertificateInformationAsync ( Context context )
69+ {
70+ // Only operate on author signatures. Packages that only have a repository signature are not interesting
71+ // for this purpose.
72+ if ( context . PrimarySignature . Type != SignatureType . Author )
73+ {
74+ return ;
5775 }
76+
77+ var hashedCertificate = context
78+ . Author
79+ . Certificates
80+ . SignatureEndCertificate ;
81+ var certificate = hashedCertificate . Certificate ;
82+ var thumbprint = hashedCertificate . Thumbprint ;
83+
84+ // Fetch the certificate record from the gallery database using the SHA-256 thumbprint.
85+ var certificateRecord = await _galleryEntitiesContext
86+ . Certificates
87+ . Where ( c => c . Thumbprint == thumbprint )
88+ . FirstOrDefaultAsync ( ) ;
89+ if ( certificateRecord == null )
90+ {
91+ _logger . LogWarning (
92+ "No certificate record was found in the gallery database for thumbprint {Thumbprint}." ,
93+ thumbprint ) ;
94+ return ;
95+ }
96+
97+ // Do nothing if the certificate details are already populated.
98+ var expiration = certificate . NotAfter . ToUniversalTime ( ) ;
99+ var subject = NoLongerThanOrNull ( certificate . Subject ) ;
100+ var issuer = NoLongerThanOrNull ( certificate . Issuer ) ;
101+ var shortSubject = NoLongerThanOrNull ( certificate . GetNameInfo ( X509NameType . SimpleName , forIssuer : false ) ) ;
102+ var shortIssuer = NoLongerThanOrNull ( certificate . GetNameInfo ( X509NameType . SimpleName , forIssuer : true ) ) ;
103+ if ( certificateRecord . Expiration == expiration
104+ && certificateRecord . Subject == subject
105+ && certificateRecord . Issuer == issuer
106+ && certificateRecord . ShortSubject == shortSubject
107+ && certificateRecord . ShortIssuer == shortIssuer )
108+ {
109+ return ;
110+ }
111+
112+ // Save the certificate details to the gallery record.
113+ certificateRecord . Expiration = expiration ;
114+ certificateRecord . Subject = subject ;
115+ certificateRecord . Issuer = issuer ;
116+ certificateRecord . ShortSubject = shortSubject ;
117+ certificateRecord . ShortIssuer = shortIssuer ;
118+
119+ await _galleryEntitiesContext . SaveChangesAsync ( ) ;
120+
121+ _logger . LogInformation (
122+ "Gallery certificate information for certificate with thumbprint {Thumbprint} has been populated." ,
123+ thumbprint ) ;
124+ }
125+
126+ private string NoLongerThanOrNull ( string input )
127+ {
128+ if ( string . IsNullOrWhiteSpace ( input ) ||
129+ input . Length > _configuration . Value . MaxCertificateStringLength )
130+ {
131+ return null ;
132+ }
133+
134+ return input ;
58135 }
59136
60137 private static void ExtractSignaturesAndCertificates ( Context context )
@@ -260,7 +337,7 @@ private async Task<PackageSignature> InitializePackageSignatureAsync(
260337 IReadOnlyDictionary < string , EndCertificate > thumbprintToEndCertificate ,
261338 bool replacePackageSignature )
262339 {
263- var packageSignatures = await _entitiesContext
340+ var packageSignatures = await _validationEntitiesContext
264341 . PackageSignatures
265342 . Include ( x => x . TrustedTimestamps )
266343 . Include ( x => x . EndCertificate )
@@ -309,10 +386,10 @@ private async Task<PackageSignature> InitializePackageSignatureAsync(
309386 // explicit and to facilitate unit testing, we explicitly remove them.
310387 foreach ( var trustedTimestamp in packageSignature . TrustedTimestamps )
311388 {
312- _entitiesContext . TrustedTimestamps . Remove ( trustedTimestamp ) ;
389+ _validationEntitiesContext . TrustedTimestamps . Remove ( trustedTimestamp ) ;
313390 }
314391
315- _entitiesContext . PackageSignatures . Remove ( packageSignature ) ;
392+ _validationEntitiesContext . PackageSignatures . Remove ( packageSignature ) ;
316393
317394 packageSignature = InitializePackageSignature (
318395 packageKey ,
@@ -356,7 +433,7 @@ private PackageSignature InitializePackageSignature(
356433 } ;
357434
358435 packageSignature . EndCertificateKey = packageSignature . EndCertificate . Key ;
359- _entitiesContext . PackageSignatures . Add ( packageSignature ) ;
436+ _validationEntitiesContext . PackageSignatures . Add ( packageSignature ) ;
360437
361438 return packageSignature ;
362439 }
@@ -394,7 +471,7 @@ private void InitializeTrustedTimestamp(
394471 } ;
395472 trustedTimestamp . EndCertificateKey = trustedTimestamp . EndCertificate . Key ;
396473 packageSignature . TrustedTimestamps . Add ( trustedTimestamp ) ;
397- _entitiesContext . TrustedTimestamps . Add ( trustedTimestamp ) ;
474+ _validationEntitiesContext . TrustedTimestamps . Add ( trustedTimestamp ) ;
398475 }
399476 else
400477 {
@@ -454,7 +531,7 @@ private void ConnectCertificates(
454531 EndCertificate = endCertificateEntity ,
455532 ParentCertificate = parentCertificateEntity ,
456533 } ;
457- _entitiesContext . CertificateChainLinks . Add ( link ) ;
534+ _validationEntitiesContext . CertificateChainLinks . Add ( link ) ;
458535 endCertificateEntity . CertificateChainLinks . Add ( link ) ;
459536 parentCertificateEntity . CertificateChainLinks . Add ( link ) ;
460537
@@ -476,7 +553,7 @@ private async Task<IReadOnlyDictionary<string, EndCertificate>> InitializeEndCer
476553
477554 // Find all of the end certificate entities that intersect with the set of certificates found in the
478555 // package that is currently being processed.
479- var existingEntities = await _entitiesContext
556+ var existingEntities = await _validationEntitiesContext
480557 . EndCertificates
481558 . Include ( x => x . CertificateChainLinks )
482559 . Where ( x => thumbprints . Contains ( x . Thumbprint ) )
@@ -495,7 +572,7 @@ private async Task<IReadOnlyDictionary<string, EndCertificate>> InitializeEndCer
495572 Thumbprint = certificateAndUse . Certificate . Thumbprint ,
496573 CertificateChainLinks = new List < CertificateChainLink > ( ) ,
497574 } ;
498- _entitiesContext . EndCertificates . Add ( entity ) ;
575+ _validationEntitiesContext . EndCertificates . Add ( entity ) ;
499576
500577 thumbprintToEntity [ certificateAndUse . Certificate . Thumbprint ] = entity ;
501578 }
@@ -524,7 +601,7 @@ private async Task<IReadOnlyDictionary<string, ParentCertificate>> InitializePar
524601
525602 // Find all of the parent certificate entities that intersect with the set of certificates found in the
526603 // package that is currently being processed.
527- var existingEntities = await _entitiesContext
604+ var existingEntities = await _validationEntitiesContext
528605 . ParentCertificates
529606 . Include ( x => x . CertificateChainLinks )
530607 . Where ( x => thumbprints . Contains ( x . Thumbprint ) )
@@ -541,7 +618,7 @@ private async Task<IReadOnlyDictionary<string, ParentCertificate>> InitializePar
541618 Thumbprint = certificate . Thumbprint ,
542619 CertificateChainLinks = new List < CertificateChainLink > ( ) ,
543620 } ;
544- _entitiesContext . ParentCertificates . Add ( entity ) ;
621+ _validationEntitiesContext . ParentCertificates . Add ( entity ) ;
545622
546623 thumbprintToEntity [ certificate . Thumbprint ] = entity ;
547624 }
0 commit comments