1
+ namespace Microsoft . Azure . Build . Tasks
2
+ {
3
+ using Microsoft . Build . Framework ;
4
+ using Microsoft . Build . Utilities ;
5
+ using System ;
6
+ using System . Collections . Generic ;
7
+ using System . IO ;
8
+ using System . Linq ;
9
+ using System . Management . Automation ;
10
+ using System . Text ;
11
+
12
+ /// <summary>
13
+ /// Authenticode Signature task
14
+ /// Flow:
15
+ /// FilesToCheckAuthCodeSignature:
16
+ /// Support 1 or many files provided
17
+ /// ProbingDirectory:
18
+ /// When provided value to this property, we will ignore FilesToCheckAuthCodeSignature property
19
+ /// FileFilterPattern:
20
+ /// This is applicable when probingDirectory is specified and you want to filter selected group of files from contents of the directory (recurrsively searched)
21
+ /// E.g. if FileFilterPattern specified = "microsoft.*.dll;system.*.dll;*.exe"
22
+ /// We will first find all the files microsoft*.dll, then system.*.dll and finally *.exe
23
+ /// All three set of search results will be combined and will then be verified for Authenticode Signature
24
+ /// </summary>
25
+ public class VerifyAuthenticodeSignatureTask : Task
26
+ {
27
+ /// <summary>
28
+ /// Gets or sets the assembly on which authenticode signature verification is performed.
29
+ /// </summary>
30
+ //public ITaskItem SignedFilePath { get; set; }
31
+
32
+ /// <summary>
33
+ /// Gets or sets list of files/assemblies for which authenticode signature verification is performed
34
+ /// If ProbingDirectory is specified, FilesToCheckAuthCodeSignature collection is ignored
35
+ /// </summary>
36
+ public ITaskItem [ ] FilesToCheckAuthCodeSignature { get ; set ; }
37
+
38
+ /// <summary>
39
+ /// Specify Directory path under which all the files will be verified for Authenticode Signature
40
+ ///
41
+ /// </summary>
42
+ public string ProbingDirectory { get ; set ; }
43
+
44
+ /// <summary>
45
+ /// Specifiy file filter pattern (e.g. *.dll,*.ps1)
46
+ /// In above example, we will first find all dll files, then combine with all the ps1 files that are found
47
+ /// </summary>
48
+ public string FileFilterPattern { get ; set ; }
49
+
50
+ /// <summary>
51
+ /// Returns True if any error occurs, False if no AutheticodeSignature occured
52
+ /// </summary>
53
+ [ Output ]
54
+ public bool AuthCodeSignTaskErrorsDetected { get ; private set ; }
55
+
56
+
57
+ private List < string > ErrorList { get ; set ; }
58
+ private bool IsFileSigned { get ; set ; }
59
+
60
+ /// <summary>
61
+ /// Execute VerifyAuthenticodeSignature task
62
+ /// </summary>
63
+ /// <returns>True: if files are authenticode signed, False: if any of the files provided are not authenticode signed</returns>
64
+ public override bool Execute ( )
65
+ {
66
+ ErrorList = new List < string > ( ) ;
67
+ AuthCodeSignTaskErrorsDetected = false ;
68
+ IsFileSigned = true ;
69
+
70
+ string [ ] filesToCheck = GetFilesToVerifyAuthCodeSignature ( ) ;
71
+ IsFileSigned = VerifyAllFiles ( filesToCheck ) ;
72
+
73
+ if ( ErrorList . Count > 0 )
74
+ {
75
+ StringBuilder sb = new StringBuilder ( ) ;
76
+ IsFileSigned = false ;
77
+ AuthCodeSignTaskErrorsDetected = true ;
78
+
79
+ ErrorList . ForEach ( ( err ) => sb . AppendLine ( err ) ) ;
80
+
81
+ Log . LogError ( "Errors detected during AuthenticodeSignature Verification" ) ;
82
+ Log . LogError ( sb . ToString ( ) ) ;
83
+ }
84
+
85
+ return IsFileSigned ;
86
+ }
87
+
88
+ /// <summary>
89
+ /// Get set of files that will ultimately be verified for AuthCode Signature
90
+ /// If we find user has sepcified a probing directory, we will give priority to it and will ignore FilesToCheckAuthCodeSignature collection
91
+ /// </summary>
92
+ /// <returns></returns>
93
+ private string [ ] GetFilesToVerifyAuthCodeSignature ( )
94
+ {
95
+ bool isProbingDirValid = false ;
96
+ string [ ] filesToCheck = null ;
97
+
98
+ //First priority to probing directory
99
+ if ( ! string . IsNullOrEmpty ( ProbingDirectory ) )
100
+ {
101
+ if ( Directory . Exists ( ProbingDirectory ) )
102
+ {
103
+ isProbingDirValid = true ;
104
+ filesToCheck = ApplyFileFilter ( ProbingDirectory ) ;
105
+ }
106
+ }
107
+
108
+ //if Probing directory is not specified then we honor all the files provided
109
+ if ( ( isProbingDirValid == false ) && ( FilesToCheckAuthCodeSignature != null ) )
110
+ {
111
+ if ( FilesToCheckAuthCodeSignature . Length > 0 )
112
+ {
113
+ filesToCheck = FilesToCheckAuthCodeSignature . Select < ITaskItem , string > ( ( item ) => item . ItemSpec ) . ToArray < string > ( ) ;
114
+ }
115
+ }
116
+
117
+ return filesToCheck ;
118
+ }
119
+
120
+ /// <summary>
121
+ /// Apply filters to filter list of files that will be checked for authenticode signed
122
+ /// </summary>
123
+ /// <param name="dirToProbeForFiles"></param>
124
+ /// <returns></returns>
125
+ private string [ ] ApplyFileFilter ( string dirToProbeForFiles )
126
+ {
127
+ string [ ] filteredFiles = null ;
128
+ IEnumerable < string > startupCollection = new string [ ] { "" } ;
129
+ IEnumerable < string > files = startupCollection . Except < string > ( new string [ ] { "" } ) ; //we do this to construct an empty IEnumerable (TODO: is there a better way)
130
+
131
+ if ( string . IsNullOrEmpty ( FileFilterPattern ) )
132
+ {
133
+ files = Directory . EnumerateFiles ( dirToProbeForFiles , "*" , SearchOption . AllDirectories ) ;
134
+ }
135
+ else
136
+ {
137
+ string [ ] listOfFilters = FileFilterPattern . Split ( new char [ ] { ';' } , StringSplitOptions . RemoveEmptyEntries ) ;
138
+ foreach ( string filter in listOfFilters )
139
+ {
140
+ files = files . Concat < string > ( Directory . EnumerateFiles ( dirToProbeForFiles , filter , SearchOption . AllDirectories ) ) ;
141
+ }
142
+ }
143
+
144
+ if ( files . Any < string > ( ) || files != null )
145
+ filteredFiles = files . ToArray < string > ( ) ;
146
+
147
+ return filteredFiles ;
148
+ }
149
+
150
+ /// <summary>
151
+ /// Verify if file is Authenticode Signed using PS cmdLet Get-AuthenticodeSignature
152
+ /// </summary>
153
+ /// <returns>True: If signature status is Valid, False: if signature status is other than Valid</returns>
154
+ private bool VerifyAllFiles ( string [ ] signedFilesArray )
155
+ {
156
+ bool isSigned = true ;
157
+ if ( signedFilesArray . Length > 0 )
158
+ {
159
+ bool authSigned = false ;
160
+ for ( int i = 0 ; i <= signedFilesArray . Length - 1 ; i ++ )
161
+ {
162
+ authSigned = VerifyAuthenticodeSignature ( signedFilesArray [ i ] ) ;
163
+ isSigned = isSigned && authSigned ;
164
+ }
165
+ }
166
+
167
+ return isSigned ;
168
+ }
169
+
170
+ /// <summary>
171
+ /// Check for Authenticode Signature
172
+ /// </summary>
173
+ /// <param name="providedFilePath"></param>
174
+ /// <returns></returns>
175
+ private bool VerifyAuthenticodeSignature ( string providedFilePath )
176
+ {
177
+ bool isSigned = true ;
178
+ string fileName = Path . GetFileName ( providedFilePath ) ;
179
+ string calculatedFullPath = Path . GetFullPath ( providedFilePath ) ;
180
+
181
+ if ( File . Exists ( calculatedFullPath ) )
182
+ {
183
+ Log . LogMessage ( string . Format ( "Verifying file '{0}'" , calculatedFullPath ) ) ;
184
+ using ( PowerShell ps = PowerShell . Create ( ) )
185
+ {
186
+ ps . AddCommand ( "Get-AuthenticodeSignature" , true ) ;
187
+ ps . AddParameter ( "FilePath" , calculatedFullPath ) ;
188
+ var cmdLetResults = ps . Invoke ( ) ;
189
+
190
+ foreach ( PSObject result in cmdLetResults )
191
+ {
192
+ Signature s = ( Signature ) result . BaseObject ;
193
+ isSigned = s . Status . Equals ( SignatureStatus . Valid ) ;
194
+ if ( isSigned == false )
195
+ {
196
+ ErrorList . Add ( string . Format ( "!!!AuthenticodeSignature status is '{0}' for file '{1}' !!!" , s . Status . ToString ( ) , calculatedFullPath ) ) ;
197
+ }
198
+ else
199
+ {
200
+ Log . LogMessage ( string . Format ( "!!!AuthenticodeSignature status is '{0}' for file '{1}' !!!" , s . Status . ToString ( ) , calculatedFullPath ) ) ;
201
+ }
202
+ break ;
203
+ }
204
+ }
205
+ }
206
+ else
207
+ {
208
+ ErrorList . Add ( string . Format ( "File '{0}' does not exist. Unable to verify AuthenticodeSignature" , calculatedFullPath ) ) ;
209
+ isSigned = false ;
210
+ }
211
+
212
+ return isSigned ;
213
+ }
214
+ }
215
+ }
0 commit comments