| File: CatalogMemberEncoder.cs | Web Access |
| Project: src\arcade\src\Microsoft.DotNet.Build.Tasks.FileCatalog\Microsoft.DotNet.Build.Tasks.FileCatalog.csproj (Microsoft.DotNet.Build.Tasks.FileCatalog) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Formats.Asn1; namespace Microsoft.DotNet.Build.Tasks.FileCatalog { /// <summary> /// DER-encodes the per-member ASN.1 structures inside a Microsoft Catalog (.cat) file, /// matching the layout produced by <c>makecat.exe</c> for a V2 (SHA-256) catalog. /// /// For each cataloged file, makecat emits two <c>TrustedSubject</c> members: /// one keyed by the raw SHA-1 hash and one keyed by the raw SHA-256 hash. Both carry a /// <c>CAT_MEMBERINFO2</c> marker attribute; the SHA-256 member additionally carries an /// <c>SPC_INDIRECT_DATA</c> attribute holding the SHA-256 digest. /// </summary> internal static class CatalogMemberEncoder { /// <summary> /// Writes a single <c>TrustedSubject</c> SEQUENCE. /// </summary> /// <param name="writer">The DER writer.</param> /// <param name="identifier">The raw hash bytes that identify the member.</param> /// <param name="sha256Digest"> /// When non-null, the member is a SHA-256 member and also carries an /// <c>SPC_INDIRECT_DATA</c> attribute with this digest (equal to <paramref name="identifier"/>). /// When null, the member is a SHA-1 member carrying only <c>CAT_MEMBERINFO2</c>. /// </param> public static void WriteTrustedSubject(AsnWriter writer, byte[] identifier, byte[]? sha256Digest) { writer.PushSequence(); // subjectIdentifier: OCTET STRING of the raw hash bytes. writer.WriteOctetString(identifier); // subjectAttributes: SET OF Attribute (DER-sorted by PushSetOf). writer.PushSetOf(); WriteCatMemberInfo2Attribute(writer); if (sha256Digest is not null) { WriteSpcIndirectDataAttribute(writer, sha256Digest); } writer.PopSetOf(); writer.PopSequence(); } /// <summary> /// Writes the CAT_MEMBERINFO2 attribute: /// Attribute { OID 1.3.6.1.4.1.311.12.2.3, SET { [2] <empty> } } /// (encoded bytes: 30 10 06 0a 2b 06 01 04 01 82 37 0c 02 03 31 02 82 00) /// </summary> private static void WriteCatMemberInfo2Attribute(AsnWriter writer) { writer.PushSequence(); writer.WriteObjectIdentifier(CatalogOids.CatMemberInfo2); writer.PushSetOf(); // A single context-[2] element with empty content ("82 00"). writer.WriteNull(new Asn1Tag(TagClass.ContextSpecific, tagValue: 2)); writer.PopSetOf(); writer.PopSequence(); } /// <summary> /// Writes the SPC_INDIRECT_DATA attribute carrying the SHA-256 file hash: /// Attribute { OID 1.3.6.1.4.1.311.2.1.4, /// SET { SEQUENCE { /// data SEQUENCE { OID 1.3.6.1.4.1.311.2.1.25, [2] { [0] <empty> } }, /// digest SEQUENCE { SEQUENCE { OID sha256, NULL }, OCTET STRING <hash> } } } } /// </summary> private static void WriteSpcIndirectDataAttribute(AsnWriter writer, byte[] sha256Digest) { writer.PushSequence(); writer.WriteObjectIdentifier(CatalogOids.SpcIndirectData); writer.PushSetOf(); // SpcIndirectDataContent writer.PushSequence(); // data: SpcAttributeTypeAndOptionalValue { type, value } writer.PushSequence(); writer.WriteObjectIdentifier(CatalogOids.SpcLinkData); // value: file [2] EXPLICIT { unicode [0] IMPLICIT BMPString "" } -> a2 02 80 00 Asn1Tag fileTag = new(TagClass.ContextSpecific, tagValue: 2, isConstructed: true); writer.PushSequence(fileTag); Asn1Tag unicodeTag = new(TagClass.ContextSpecific, tagValue: 0, isConstructed: false); writer.WriteCharacterString(UniversalTagNumber.BMPString, string.Empty, unicodeTag); writer.PopSequence(fileTag); writer.PopSequence(); // messageDigest: DigestInfo { AlgorithmIdentifier { OID sha256, NULL }, OCTET STRING digest } writer.PushSequence(); writer.PushSequence(); writer.WriteObjectIdentifier(CatalogOids.Sha256); writer.WriteNull(); writer.PopSequence(); writer.WriteOctetString(sha256Digest); writer.PopSequence(); writer.PopSequence(); // SpcIndirectDataContent writer.PopSetOf(); writer.PopSequence(); } } }