/// <summary> /// Opens a read-only stream for the package signature file. /// </summary> /// <remarks>Callers should first verify that a package is signed before calling this method.</remarks> /// <param name="reader">A binary reader for a signed package.</param> /// <returns>A readable stream.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="reader" /> is <c>null</c>.</exception> /// <exception cref="SignatureException">Thrown if a package signature file is invalid or missing.</exception> public static Stream OpenPackageSignatureFileStream(BinaryReader reader) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } var metadata = SignedPackageArchiveIOUtility.ReadSignedArchiveMetadata(reader); var signatureCentralDirectoryHeader = metadata.GetPackageSignatureFileCentralDirectoryHeaderMetadata(); return(GetPackageSignatureFile(reader, signatureCentralDirectoryHeader)); }
internal static string GetPackageContentHash(BinaryReader reader) { using (var hashFunc = new Sha512HashFunction()) { // skip validating signature entry since we're just trying to get the content hash here instead of // verifying signature entry. var metadata = SignedPackageArchiveIOUtility.ReadSignedArchiveMetadata(reader, validateSignatureEntry: false); var signatureCentralDirectoryHeader = metadata.GetPackageSignatureFileCentralDirectoryHeaderMetadata(); var centralDirectoryRecordsWithoutSignature = RemoveSignatureAndOrderByOffset(metadata); // Read and hash from the start of the archive to the start of the file headers reader.BaseStream.Seek(offset: 0, origin: SeekOrigin.Begin); SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashFunc, metadata.StartOfLocalFileHeaders); // Read and hash file headers foreach (var record in centralDirectoryRecordsWithoutSignature) { reader.BaseStream.Seek(offset: record.OffsetToLocalFileHeader, origin: SeekOrigin.Begin); SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashFunc, record.OffsetToLocalFileHeader + record.FileEntryTotalSize); } // Order central directory records by their position centralDirectoryRecordsWithoutSignature.Sort((x, y) => x.Position.CompareTo(y.Position)); // Update offset of any central directory record that has a file entry after signature foreach (var record in centralDirectoryRecordsWithoutSignature) { reader.BaseStream.Seek(offset: record.Position, origin: SeekOrigin.Begin); // Hash from the start of the central directory record until the relative offset of local file header (42 from the start of central directory record, including signature length) SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashFunc, reader.BaseStream.Position + 42); var relativeOffsetOfLocalFileHeader = (uint)(reader.ReadUInt32() + record.ChangeInOffset); HashUInt32(hashFunc, relativeOffsetOfLocalFileHeader); // Continue hashing file name, extra field, and file comment fields. SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashFunc, reader.BaseStream.Position + record.HeaderSize - CentralDirectoryHeader.SizeInBytesOfFixedLengthFields); } reader.BaseStream.Seek(offset: metadata.EndOfCentralDirectory, origin: SeekOrigin.Begin); // Hash until total entries in end of central directory record (8 bytes from the start of EOCDR) SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashFunc, metadata.EndOfCentralDirectory + 8); var eocdrTotalEntries = (ushort)(reader.ReadUInt16() - 1); var eocdrTotalEntriesOnDisk = (ushort)(reader.ReadUInt16() - 1); HashUInt16(hashFunc, eocdrTotalEntries); HashUInt16(hashFunc, eocdrTotalEntriesOnDisk); // update the central directory size by substracting the size of the package signature file's central directory header var eocdrSizeOfCentralDirectory = (uint)(reader.ReadUInt32() - signatureCentralDirectoryHeader.HeaderSize); HashUInt32(hashFunc, eocdrSizeOfCentralDirectory); var eocdrOffsetOfCentralDirectory = reader.ReadUInt32() - (uint)signatureCentralDirectoryHeader.FileEntryTotalSize; HashUInt32(hashFunc, eocdrOffsetOfCentralDirectory); // Hash until the end of the reader SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashFunc, reader.BaseStream.Length); hashFunc.Update(new byte[0], offset: 0, count: 0); return(hashFunc.GetHash()); } }
/// <summary> /// Verifies that a signed package archive's signature is valid and it has not been tampered with. /// /// </summary> /// <param name="reader">Signed package to verify</param> /// <param name="hashAlgorithm">Hash algorithm to be used to hash data.</param> /// <param name="expectedHash">Hash value of the original data.</param> /// <returns>True if package archive's hash matches the expected hash</returns> internal static bool VerifySignedPackageIntegrity(BinaryReader reader, HashAlgorithm hashAlgorithm, byte[] expectedHash) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } if (hashAlgorithm == null) { throw new ArgumentNullException(nameof(hashAlgorithm)); } if (expectedHash == null || expectedHash.Length == 0) { throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(reader)); } // Make sure it is signed with a valid signature file if (!IsSigned(reader)) { throw new SignatureException(NuGetLogCode.NU3003, Strings.SignedPackageNotSignedOnVerify); } var metadata = SignedPackageArchiveIOUtility.ReadSignedArchiveMetadata(reader); var signatureCentralDirectoryHeader = metadata.GetPackageSignatureFileCentralDirectoryHeaderMetadata(); var centralDirectoryRecordsWithoutSignature = RemoveSignatureAndOrderByOffset(metadata); try { // Read and hash from the start of the archive to the start of the file headers reader.BaseStream.Seek(offset: 0, origin: SeekOrigin.Begin); SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashAlgorithm, metadata.StartOfLocalFileHeaders); // Read and hash file headers foreach (var record in centralDirectoryRecordsWithoutSignature) { reader.BaseStream.Seek(offset: record.OffsetToLocalFileHeader, origin: SeekOrigin.Begin); SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashAlgorithm, record.OffsetToLocalFileHeader + record.FileEntryTotalSize); } // Order central directory records by their position centralDirectoryRecordsWithoutSignature.Sort((x, y) => x.Position.CompareTo(y.Position)); // Update offset of any central directory record that has a file entry after signature foreach (var record in centralDirectoryRecordsWithoutSignature) { reader.BaseStream.Seek(offset: record.Position, origin: SeekOrigin.Begin); // Hash from the start of the central directory record until the relative offset of local file header (42 from the start of central directory record, including signature length) SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashAlgorithm, reader.BaseStream.Position + 42); var relativeOffsetOfLocalFileHeader = (uint)(reader.ReadUInt32() + record.ChangeInOffset); HashUInt32(hashAlgorithm, relativeOffsetOfLocalFileHeader); // Continue hashing file name, extra field, and file comment fields. SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashAlgorithm, reader.BaseStream.Position + record.HeaderSize - CentralDirectoryHeader.SizeInBytesOfFixedLengthFields); } reader.BaseStream.Seek(offset: metadata.EndOfCentralDirectory, origin: SeekOrigin.Begin); // Hash until total entries in end of central directory record (8 bytes from the start of EOCDR) SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashAlgorithm, metadata.EndOfCentralDirectory + 8); var eocdrTotalEntries = (ushort)(reader.ReadUInt16() - 1); var eocdrTotalEntriesOnDisk = (ushort)(reader.ReadUInt16() - 1); HashUInt16(hashAlgorithm, eocdrTotalEntries); HashUInt16(hashAlgorithm, eocdrTotalEntriesOnDisk); // update the central directory size by substracting the size of the package signature file's central directory header var eocdrSizeOfCentralDirectory = (uint)(reader.ReadUInt32() - signatureCentralDirectoryHeader.HeaderSize); HashUInt32(hashAlgorithm, eocdrSizeOfCentralDirectory); var eocdrOffsetOfCentralDirectory = reader.ReadUInt32() - (uint)signatureCentralDirectoryHeader.FileEntryTotalSize; HashUInt32(hashAlgorithm, eocdrOffsetOfCentralDirectory); // Hash until the end of the reader SignedPackageArchiveIOUtility.ReadAndHashUntilPosition(reader, hashAlgorithm, reader.BaseStream.Length); hashAlgorithm.TransformFinalBlock(new byte[0], inputOffset: 0, inputCount: 0); return(CompareHash(expectedHash, hashAlgorithm.Hash)); } // If exception is throw in means the archive was not a valid package. It has been tampered, return false. catch { } return(false); }