فصل ۱۷: Assembly، Manifest، Module، Strong Name و Authenticode
فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.
۱۷ — Assemblyها
Assembly واحد پایهٔ Deployment در .NET و همچنین Container همهٔ Typeها است. یک Assembly شامل Typeهای Compileشده همراه با کد Intermediate Language یا IL آنها، Resourceهای Runtime و اطلاعاتی برای کمک به Versioning و Reference دادن به Assemblyهای دیگر است. Assembly همچنین یک Boundary برای Type Resolution تعریف میکند. در .NET، یک Assembly از یک File با پسوند .dll تشکیل میشود.
بیشتر Typeهای این فصل از Namespaceهای زیر میآیند:
System.Reflection
System.Resources
System.Globalization
داخل یک Assembly چه چیزهایی وجود دارد؟
یک Assembly چهار نوع محتوا دارد:
- Assembly Manifest
- اطلاعاتی برای CLR فراهم میکند؛ مانند نام Assembly، Version آن و Assemblyهای دیگری که Reference میکند.
- Application Manifest
- اطلاعاتی برای Operating System فراهم میکند؛ مانند نحوهٔ Deploy شدن Assembly و اینکه آیا Administrative Elevation لازم است یا نه.
- Compiled Types
- کد Compileشدهٔ IL و Metadata مربوط به Typeهای تعریفشده در Assembly.
- Resources
- دادههای دیگری که داخل Assembly Embed شدهاند، مانند Imageها و Text قابل Localization.
از میان این موارد، فقط Assembly Manifest اجباری است، هرچند تقریباً همیشه Assembly شامل Typeهای Compileشده نیز هست؛ مگر اینکه Resource Assembly باشد که بعداً توضیح داده میشود.
Assembly Manifest
Assembly Manifest دو وظیفه دارد:
- Assembly را برای Managed Hosting Environment توصیف میکند.
- بهعنوان Directory مربوط به Moduleها، Typeها و Resourceهای داخل Assembly عمل میکند.
بنابراین Assemblyها Self-Describing هستند. مصرفکننده میتواند همهٔ Data، Typeها و Functionهای یک Assembly را بدون نیاز به Fileهای اضافی کشف کند.
خلاصهٔ Data مهم از نظر Functionality که در Manifest ذخیره میشود:
- Simple Name مربوط به Assembly
- شمارهٔ Version با
AssemblyVersion - Public Key و Hash امضاشدهٔ Assembly، اگر Strongly Named باشد
- فهرست Assemblyهای Referenceشده، شامل Version و Public Key آنها
- فهرست Typeهای تعریفشده در Assembly
- Culture هدف، اگر Satellite Assembly باشد، با
AssemblyCulture
Manifest میتواند اطلاعات توصیفی زیر را نیز ذخیره کند:
- Title و Description کامل با
AssemblyTitle و AssemblyDescription - اطلاعات Company و Copyright با
AssemblyCompany و AssemblyCopyright - Display Version با
AssemblyInformationalVersion - Attributeهای اضافی برای Data سفارشی
بخشی از این Data از Argumentهای دادهشده به Compiler، مانند فهرست Assemblyهای Referenceشده یا Public Key مورد استفاده برای Signing، به دست میآید. بقیه از Assembly Attributeهایی میآید که در پرانتز اشاره شدند.
مشخصکردن Assembly Attributeها
Assembly Attributeهای رایج را میتوان در Visual Studio، در صفحهٔ Properties پروژه و Tab مربوط به Package تعیین کرد. تنظیمات آن Tab به Project File با پسوند .csproj افزوده میشوند.
برای Attributeهایی که Package Tab پشتیبانی نمیکند، یا هنگامی که با .csproj کار نمیکنید، میتوانید Assembly Attributeها را در Source Code تعیین کنید؛ این کار اغلب در Fileای به نام AssemblyInfo.cs انجام میشود.
یک File اختصاصی برای Attributeها فقط شامل using statementها و اعلانهای Assembly Attribute است. برای مثال، برای آشکار کردن Typeهای با Scope داخلی به یک Unit Test Project، چنین مینویسید:
using System.Runtime.CompilerServices;
[assembly:InternalsVisibleTo("MyUnitTestProject")]
Application Manifest در Windows
Application Manifest یک XML File است که اطلاعات مربوط به Assembly را به OS منتقل میکند. هنگام Build، Application Manifest بهعنوان Win32 Resource در Startup Executable Embed میشود. اگر وجود داشته باشد، پیش از آنکه CLR Assembly را Load کند خوانده و پردازش میشود و میتواند روی نحوهٔ Launch شدن Process برنامه توسط Windows اثر بگذارد.
Application Manifest مربوط به .NET یک Root Element به نام assembly در XML Namespace به نام urn:schemas-microsoft-com:asm.v1 دارد:
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- contents of manifest -->
</assembly>
Manifest زیر به OS میگوید Administrative Elevation درخواست کند:
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
Applicationهای UWP یک Manifest بسیار مفصلتر دارند که در File به نام Package.appxmanifest توصیف میشود. این Manifest شامل اعلان Capabilityهای برنامه است که Permissionهای دادهشده از طرف OS را تعیین میکنند. سادهترین راه ویرایش این File با Visual Studio است که هنگام Double-Click روی Manifest یک Dialog نشان میدهد.
Deploy کردن Application Manifest
برای افزودن Application Manifest به Project .NET در Visual Studio، روی Project در Solution Explorer راستکلیک کنید، Add و سپس New Item را بزنید و بعد Application Manifest File را انتخاب کنید. هنگام Build، Manifest داخل Output Assembly Embed میشود.
Moduleها
محتوای Assembly در واقع داخل یک Container میانی به نام Module بستهبندی میشود. هر Module متناظر با Fileای است که محتوای Assembly را نگه میدارد. علت این لایهٔ Container اضافی این است که اجازه دهد یک Assembly چند File را در بر بگیرد؛ قابلیتی که در .NET Framework وجود داشت، اما در .NET 5+ و .NET Core وجود ندارد. شکل 17-1 این رابطه را نشان میدهد.
شکل 17-1 — Assembly تکفایلی: Manifest اجباری و IL/Type Metadata و Resourceهای اختیاری در یک Module.اگرچه .NET از Multifile Assembly پشتیبانی نمیکند، گاهی باید از سطح Container اضافیای که Moduleها ایجاد میکنند آگاه باشید. سناریوی اصلی Reflection است؛ از جمله Reflecting Assemblies و Emitting Assemblies and Types.
کلاس Assembly
کلاس Assembly در System.Reflection دروازهٔ دسترسی به Assembly Metadata در Runtime است. راههای مختلفی برای گرفتن یک Assembly Object وجود دارد؛ سادهترین راه property به نام Assembly روی یک Type است:
Assembly a = typeof (Program).Assembly;
همچنین میتوانید با یکی از متدهای static کلاس Assembly یک Assembly Object بگیرید:
GetExecutingAssembly- Assembly مربوط به Typeای را برمیگرداند که Function در حال اجرا را تعریف کرده است.
GetCallingAssembly- مانند
GetExecutingAssembly است، اما برای Functionای که Function فعلی را فراخوانی کرده است. GetEntryAssembly- Assemblyای را برمیگرداند که Entry Method اصلی Application را تعریف کرده است.
پس از داشتن یک Assembly Object، میتوانید با Propertyها و Methodهایش Metadata Assembly را Query کنید و Typeهای آن را با Reflection بررسی کنید. جدول 17-1 خلاصهای از این Functionها را نشان میدهد.
جدول 17-1 — اعضای Assembly| Functionها | هدف | بخش مرتبط |
|---|
FullName, GetName | نام Fully Qualified یا یک AssemblyName را برمیگرداند. | Assembly Names |
CodeBase, Location | Location مربوط به Assembly File | Loading, Resolving, and Isolating Assemblies |
Load, LoadFrom, LoadFile | Assembly را بهصورت دستی در Memory Load میکند. | Loading, Resolving, and Isolating Assemblies |
GetSatelliteAssembly | Satellite Assembly مربوط به Culture دادهشده را پیدا میکند. | Resources and Satellite Assemblies |
GetType, GetTypes | یک Type یا همهٔ Typeهای تعریفشده در Assembly را برمیگرداند. | Reflecting and Activating Types |
EntryPoint | Entry Method برنامه را بهصورت MethodInfo برمیگرداند. | Reflecting and Invoking Members |
GetModule, GetModules, ManifestModule | همهٔ Moduleها یا Main Module یک Assembly را برمیگرداند. | Reflecting Assemblies |
GetCustomAttribute, GetCustomAttributes | Attributeهای Assembly را برمیگرداند. | Working with Attributes |
Strong Name و Assembly Signing
یک Strongly Named Assembly هویت یکتا دارد. این کار با افزودن دو بخش Metadata به Manifest انجام میشود:
- یک شمارهٔ یکتا که متعلق به نویسندگان Assembly است.
- یک Hash امضاشده از Assembly که اثبات میکند دارندهٔ آن شمارهٔ یکتا Assembly را تولید کرده است.
برای این کار به یک Public/Private Key Pair نیاز است. Public Key شمارهٔ شناسایی یکتا را فراهم میکند و Private Key امکان Signing را میدهد.
Public Key برای تضمین یکتایی Assembly Referenceها ارزشمند است: یک Strongly Named Assembly، Public Key را بخشی از Identity خود میکند.
در .NET Framework، Private Key از Assembly در برابر Tampering محافظت میکند؛ بدون Private Key شما، شخص دیگری نمیتواند نسخهٔ تغییریافتهٔ Assembly را منتشر کند بدون اینکه Signature خراب شود. در عمل این موضوع هنگام Load کردن Assembly در Global Assembly Cache مربوط به .NET Framework کاربرد دارد. در .NET 5+ و .NET Core، Signature کاربرد کمی دارد، زیرا هرگز Check نمیشود.
افزودن Strong Name به Assemblyای که قبلاً Weak Named بوده Identity آن را تغییر میدهد. به همین دلیل اگر فکر میکنید Assembly در آینده ممکن است Strong Name لازم داشته باشد، بهتر است از همان ابتدا Strong Name داشته باشد.
چگونه Assembly را Strongly Name کنیم
ابتدا با Utility به نام sn.exe یک Public/Private Key Pair بسازید:
این Command یک Key Pair جدید میسازد و آن را در Fileای به نام MyKeyPair.snk ذخیره میکند. اگر بعداً این File را گم کنید، توانایی Recompile کردن Assembly با همان Identity را برای همیشه از دست خواهید داد.
با Update کردن Project File میتوانید Assembly را با این File Sign کنید. در Visual Studio به Project Properties بروید و در Tab مربوط به Signing، گزینهٔ «Sign the assembly» را فعال کرده و File با پسوند .snk را انتخاب کنید.
همان Key Pair میتواند چند Assembly را Sign کند؛ اگر Simple Nameهای آنها متفاوت باشد، Identityهای آنها همچنان متفاوت خواهد بود.
Assembly Nameها
Identity یک Assembly از چهار قطعهٔ Metadata در Manifest تشکیل میشود:
- Simple Name
- Version؛ اگر وجود نداشته باشد
0.0.0.0 - Culture؛ اگر Satellite نباشد
neutral - Public Key Token؛ اگر Strongly Named نباشد
null
Simple Name از هیچ Attributeای نمیآید، بلکه از نام Fileای میآید که Assembly در ابتدا به آن Compile شده است، با حذف Extension. بنابراین Simple Name مربوط به System.Xml.dll برابر System.Xml است. Rename کردن File، Simple Name داخل Assembly را عوض نمیکند.
Version Number از Attribute به نام AssemblyVersion میآید و Stringی است که به چهار بخش تقسیم میشود:
major.minor.build.revision
Version را میتوان چنین تعیین کرد:
[assembly: AssemblyVersion ("2.5.6.7")]
Culture از Attribute به نام AssemblyCulture میآید و روی Satellite Assemblyها اعمال میشود. Public Key Token نیز از Strong Name دادهشده هنگام Compile میآید.
Fully Qualified Nameها
Fully Qualified Assembly Name رشتهای است که هر چهار مؤلفهٔ Identity را با قالب زیر در بر میگیرد:
simple-name, Version=version, Culture=culture, PublicKeyToken=public-key
برای مثال Fully Qualified Name مربوط به System.Private.CoreLib.dll چنین است:
System.Private.CoreLib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=7cec85d7bea7798e
اگر Assembly هیچ AssemblyVersion نداشته باشد، Version به شکل 0.0.0.0 ظاهر میشود. اگر Unsigned باشد، Public Key Token به شکل null نمایش داده میشود.
property به نام FullName در یک Assembly، Fully Qualified Name را برمیگرداند. Compiler هنگام ثبت Assembly Referenceها در Manifest همیشه از Fully Qualified Name استفاده میکند.
کلاس AssemblyName
AssemblyName کلاسی است که برای هر یک از چهار مؤلفهٔ Fully Qualified Assembly Name یک Property strongly typed دارد. دو هدف اصلی دارد:
- یک Fully Qualified Assembly Name را Parse یا Build میکند.
- مقداری Data اضافه برای کمک به Resolve کردن یا یافتن Assembly نگه میدارد.
یک AssemblyName را به هر یک از روشهای زیر میتوانید بگیرید:
- نمونهسازی
AssemblyName و دادن Fully Qualified Name. - فراخوانی
GetName روی یک Assembly موجود. - فراخوانی
AssemblyName.GetAssemblyName و دادن Path یک Assembly File روی Disk.
همچنین میتوانید AssemblyName را بدون Argument بسازید و سپس هر Property را تنظیم کنید تا Fully Qualified Name ساخته شود. وقتی به این شکل Construct شود Mutable است.
Propertyها و Methodهای اصلی:
string FullName { get; } // Fully qualified name
string Name { get; set; } // Simple name
Version Version { get; set; } // Assembly version
CultureInfo CultureInfo { get; set; } // For satellite assemblies
string CodeBase { get; set; } // Location
byte[] GetPublicKey(); // 160 bytes
void SetPublicKey (byte[] key);
byte[] GetPublicKeyToken(); // 8-byte version
void SetPublicKeyToken (byte[] publicKeyToken);
Version خودش یک نمایش strongly typed است و Propertyهایی برای Major، Minor، Build و Revision دارد. GetPublicKey کل Cryptographic Public Key را برمیگرداند؛ GetPublicKeyToken هشت Byte آخر را که برای ایجاد Identity استفاده میشوند برمیگرداند.
برای گرفتن Simple Name یک Assembly با AssemblyName:
Console.WriteLine (typeof (string).Assembly.GetName().Name);
// System.Private.CoreLib
برای گرفتن Version:
string v = myAssembly.GetName().Version.ToString();
Assembly Informational Version و File Version
دو Assembly Attribute دیگر برای بیان اطلاعات Version وجود دارد. برخلاف AssemblyVersion، دو Attribute زیر روی Identity یک Assembly اثر نمیگذارند و بنابراین روی رفتار Compile-Time یا Runtime اثری ندارند:
AssemblyInformationalVersion- Versionی که به End User نمایش داده میشود. در Dialog مربوط به Windows File Properties با عنوان Product Version دیده میشود. هر Stringی میتواند اینجا قرار گیرد، مانند
5.1 Beta 2. معمولاً همهٔ Assemblyهای یک Application یک Informational Version یکسان میگیرند. AssemblyFileVersion- برای اشاره به Build Number همان Assembly طراحی شده است. در Windows File Properties با عنوان File Version دیده میشود. مانند
AssemblyVersion باید Stringی شامل حداکثر چهار Number جداشده با Period باشد.
Authenticode Signing
Authenticode یک سیستم Code Signing است که هدفش اثبات Identity ناشر است. Authenticode و Strong-Name Signing مستقل از هم هستند؛ میتوانید Assembly را با یکی یا هر دو Sign کنید.
اگرچه Strong-Name Signing میتواند ثابت کند Assemblyهای A، B و C از یک Party آمدهاند، با فرض اینکه Private Key فاش نشده باشد، نمیتواند بگوید آن Party چه کسی بوده است. برای دانستن اینکه Party مثلاً Joe Albahari یا Microsoft Corporation بوده است به Authenticode نیاز دارید.
Authenticode هنگام Download برنامهها از Internet مفید است، زیرا اطمینان میدهد برنامه از فرد یا سازمانی آمده که Certificate Authority نام برده و در مسیر انتقال تغییر نکرده است. همچنین هنگام اجرای نخستینبار یک Application Downloadشده، Warning مربوط به «Unknown Publisher» را حذف میکند. Authenticode Signing برای ارسال App به Windows Store نیز الزامی است.
Authenticode فقط با Assemblyهای .NET کار نمیکند؛ Executableها و Binaryهای Unmanaged مانند Deployment Fileهای .msi را نیز پشتیبانی میکند. البته Authenticode تضمین نمیکند برنامه Malware ندارد، هرچند احتمال آن را کمتر میکند؛ زیرا فرد یا سازمانی حاضر شده نام خود را، با پشتوانهٔ Passport یا Company Document، پشت Executable یا Library قرار دهد.
Signing با Authenticode مستلزم تماس با یک Certificate Authority یا CA و ارائهٔ مدرک Identity شخص یا شرکت، مانند مدارک ثبتی شرکت، است. پس از بررسی مدارک، CA یک X.509 Code-Signing Certificate صادر میکند که معمولاً یک تا پنج سال اعتبار دارد. با آن میتوانید Assemblyها را با Utility به نام signtool Sign کنید. همچنین میتوانید با Utility به نام makecert خودتان Certificate بسازید، اما چنین Certificateای فقط روی Computerهایی شناخته میشود که صریحاً روی آنها Install شده باشد.
اینکه Certificateهای Self-Signed نشده میتوانند روی هر Computer کار کنند متکی بر Public Key Infrastructure است. اساساً Certificate شما با Certificate دیگری که متعلق به CA است Sign میشود. CA مورد اعتماد است، چون CAها در OS Load شدهاند. برای دیدن آنها در Windows Control Panel، عبارت certificate را Search کنید، در Administrative Tools روی «Manage computer certificates» بزنید، Certificate Manager را باز کنید و از Trusted Root Certification Authorities وارد Certificates شوید. CA در صورت فاش شدن Certificate یک Publisher میتواند آن را Revoke کند؛ بنابراین Verification امضای Authenticode مستلزم دریافت دورهای فهرست بهروز Certificateهای Revokeشده از CA است.
از آنجا که Authenticode از Cryptographic Signing استفاده میکند، اگر کسی بعداً File را دستکاری کند Signature نامعتبر میشود. Cryptography، Hashing و Signing در فصل ۲۰ بررسی میشوند.
چگونه با Authenticode Sign کنیم
گرفتن و نصب Certificate
گام نخست گرفتن Code-Signing Certificate از یک CA است. سپس یا میتوانید با Certificate بهصورت File محافظتشده با Password کار کنید یا آن را در Certificate Store کامپیوتر Load کنید. مزیت روش دوم این است که میتوانید بدون نیاز به مشخص کردن Password Sign کنید؛ این کار مانع نمایش Password در Automated Build Scriptها یا Batch Fileها میشود.
Code-Signing Certificate را از کجا بگیریم؟
فقط تعداد محدودی Code-Signing CA بهعنوان Root Certification Authority از پیش در Windows Load شدهاند. از جمله Comodo، GoDaddy، GlobalSign، DigiCert، Thawte و Symantec.
Resellerهایی مانند K Software نیز Certificateهای Code Signing را از Authorityهای فوق با تخفیف ارائه میکنند.
Certificateهای Authenticode صادرشده توسط K Software، Comodo، GoDaddy و GlobalSign بهعنوان گزینههایی با محدودیت کمتر معرفی میشوند، چون برنامههای غیرمایکروسافتی را نیز Sign میکنند. بهجز این تفاوت، Productهای Vendorهای مختلف از نظر Functionality معادل هستند.
توجه کنید Certificate مربوط به SSL عموماً برای Authenticode Signing قابل استفاده نیست، با وجود اینکه هر دو از زیرساخت X.509 استفاده میکنند. بخشی از دلیل این است که SSL دربارهٔ اثبات مالکیت Domain است، اما Authenticode دربارهٔ اثبات Identity شماست.
برای Load کردن Certificate در Certificate Store کامپیوتر، Certificate Manager را همانطور که گفته شد باز کنید. Folder مربوط به Personal را باز کنید، روی Folder به نام Certificates راستکلیک کنید و All Tasks/Import را انتخاب کنید. Import Wizard شما را در فرایند راهنمایی میکند. پس از کاملشدن Import، دکمهٔ View را روی Certificate بزنید، به Details بروید و Thumbprint آن را Copy کنید. این همان SHA-256 Hashی است که بعداً برای شناسایی Certificate هنگام Signing لازم دارید.
Signing با signtool.exe
با Utility به نام signtool که همراه Visual Studio است میتوانید Programهای خود را Authenticode-Sign کنید. این ابزار در مسیر Microsoft SDKs\ClickOnce\SignTool زیر Program Files قرار دارد. Command زیر Fileای به نام LINQPad.exe را با Certificate موجود در My Store کامپیوتر به نام «Joseph Albahari» و Algorithm امن SHA256 Sign میکند:
signtool sign /n "Joseph Albahari" /fd sha256 LINQPad.exe
همچنین با /d و /du میتوانید Description و Product URL را مشخص کنید:
... /d LINQPad /du http://www.linqpad.net
در بیشتر موارد همچنین لازم است یک Time-Stamping Server مشخص کنید؛ ادامهٔ این موضوع در مقالهٔ بعدی آمده است.