1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package authenticode
18
19 import (
20 "bufio"
21 "bytes"
22 "context"
23 "crypto"
24 "crypto/hmac"
25 "encoding/base64"
26 "encoding/binary"
27 "errors"
28 "fmt"
29 "io"
30 "io/ioutil"
31 "path/filepath"
32 "strings"
33 "unicode/utf16"
34
35 "github.com/sassoftware/relic/lib/binpatch"
36 "github.com/sassoftware/relic/lib/certloader"
37 "github.com/sassoftware/relic/lib/pkcs7"
38 "github.com/sassoftware/relic/lib/pkcs9"
39 "github.com/sassoftware/relic/lib/x509tools"
40 "github.com/sassoftware/relic/signers/sigerrors"
41 )
42
43
44 type PsSigStyle int
45
46 const (
47
48 SigStyleHash PsSigStyle = iota + 1
49
50 SigStyleXML
51
52 SigStyleC
53 )
54
55 var psExtMap = map[string]PsSigStyle{
56 ".ps1": SigStyleHash,
57 ".ps1xml": SigStyleXML,
58 ".psc1": SigStyleXML,
59 ".psd1": SigStyleHash,
60 ".psm1": SigStyleHash,
61 ".cdxml": SigStyleXML,
62 ".mof": SigStyleC,
63 }
64
65 const psBegin = "SIG # Begin signature block"
66 const psEnd = "SIG # End signature block"
67
68 type sigStyle struct{ start, end string }
69
70 var psStyles = map[PsSigStyle]sigStyle{
71 SigStyleHash: sigStyle{"# ", ""},
72 SigStyleXML: sigStyle{"<!-- ", " -->"},
73 SigStyleC: sigStyle{"/* ", " */"},
74 }
75
76
77 func GetSigStyle(filename string) (PsSigStyle, bool) {
78 style, ok := psExtMap[filepath.Ext(filename)]
79 return style, ok
80 }
81
82
83 func AllSigStyles() []string {
84 var ret []string
85 for k := range psExtMap {
86 ret = append(ret, k)
87 }
88 return ret
89 }
90
91 type PsDigest struct {
92 Imprint []byte
93 HashFunc crypto.Hash
94 TextSize, SigSize int64
95 SigStyle PsSigStyle
96 IsUtf16 bool
97 }
98
99
100
101
102
103 func DigestPowershell(r io.Reader, style PsSigStyle, hash crypto.Hash) (*PsDigest, error) {
104 si, ok := psStyles[style]
105 if !ok {
106 return nil, errors.New("invalid powershell signature style")
107 }
108 br := bufio.NewReader(r)
109 isUtf16, first, _ := detectUtf16(br, si.start, si.end)
110 d := hash.New()
111 var textSize, sigSize int64
112 var saved string
113 for {
114 line, err := readLine(br, isUtf16)
115 if err != nil && err != io.EOF {
116 return nil, err
117 }
118 if line == first {
119
120 if isUtf16 {
121 saved = saved[:len(saved)-4]
122 sigSize = 4
123 } else {
124 saved = saved[:len(saved)-2]
125 sigSize = 2
126 }
127
128 sigSize += int64(len(line))
129 n, err := io.Copy(ioutil.Discard, br)
130 if err != nil {
131 return nil, err
132 }
133 sigSize += n
134 break
135 } else {
136 writeUtf16(d, saved, isUtf16)
137 textSize += int64(len(saved))
138 saved = line
139 }
140 if err == io.EOF {
141 break
142 }
143 }
144 writeUtf16(d, saved, isUtf16)
145 textSize += int64(len(saved))
146 return &PsDigest{d.Sum(nil), hash, textSize, sigSize, style, isUtf16}, nil
147 }
148
149 func detectUtf16(br *bufio.Reader, start, end string) (bool, string, string) {
150 first := start + psBegin + end + "\r\n"
151 last := start + psEnd + end + "\r\n"
152 if bom, err := br.Peek(2); err == nil && bom[0] == 0xff && bom[1] == 0xfe {
153
154 return true, toUtf16(first), toUtf16(last)
155 }
156 return false, first, last
157 }
158
159
160
161 func VerifyPowershell(r io.ReadSeeker, style PsSigStyle, skipDigests bool) (*pkcs9.TimestampedSignature, error) {
162 si, ok := psStyles[style]
163 if !ok {
164 return nil, errors.New("invalid powershell signature style")
165 }
166 br := bufio.NewReader(r)
167 isUtf16, first, last := detectUtf16(br, si.start, si.end)
168 found := false
169 var textSize int64
170 var pkcsb bytes.Buffer
171 for {
172 line, err := readLine(br, isUtf16)
173 if err == io.EOF && !found {
174 return nil, sigerrors.NotSignedError{Type: "powershell document"}
175 } else if err != nil {
176 return nil, err
177 }
178 if found && line == last {
179 break
180 } else if found {
181 lstr := string(line)
182 if isUtf16 {
183 lstr = fromUtf16(line)
184 }
185 if !strings.HasPrefix(lstr, si.start) || !strings.HasSuffix(lstr, si.end+"\r\n") {
186 return nil, errors.New("malformed powershell signature")
187 }
188 i := len(si.start)
189 j := len(lstr) - len(si.end) - 2
190 lder, err := base64.StdEncoding.DecodeString(lstr[i:j])
191 if err != nil {
192 return nil, err
193 }
194 pkcsb.Write(lder)
195 } else if line == first {
196
197 if isUtf16 {
198 textSize -= 4
199 } else {
200 textSize -= 2
201 }
202 found = true
203 } else {
204 textSize += int64(len(line))
205 }
206 }
207 psd, err := pkcs7.Unmarshal(pkcsb.Bytes())
208 if err != nil {
209 return nil, err
210 }
211 if !psd.Content.ContentInfo.ContentType.Equal(OidSpcIndirectDataContent) {
212 return nil, errors.New("not an authenticode signature")
213 }
214 sig, err := psd.Content.Verify(nil, false)
215 if err != nil {
216 return nil, err
217 }
218 ts, err := pkcs9.VerifyOptionalTimestamp(sig)
219 if err != nil {
220 return nil, err
221 }
222 indirect := new(SpcIndirectDataContentMsi)
223 if err := psd.Content.ContentInfo.Unmarshal(indirect); err != nil {
224 return nil, err
225 }
226 hash, err := x509tools.PkixDigestToHashE(indirect.MessageDigest.DigestAlgorithm)
227 if err != nil {
228 return nil, err
229 }
230 if !skipDigests {
231 if _, err := r.Seek(0, 0); err != nil {
232 return nil, err
233 }
234 digest, err := DigestPowershell(r, style, hash)
235 if err != nil {
236 return nil, err
237 }
238 if !hmac.Equal(digest.Imprint, indirect.MessageDigest.Digest) {
239 return nil, fmt.Errorf("digest mismatch: %x != %x", digest.Imprint, indirect.MessageDigest.Digest)
240 }
241 }
242 return &ts, nil
243 }
244
245
246 func (pd *PsDigest) Sign(ctx context.Context, cert *certloader.Certificate) (*binpatch.PatchSet, *pkcs9.TimestampedSignature, error) {
247 ts, err := SignSip(ctx, pd.Imprint, pd.HashFunc, psSipInfo, cert)
248 if err != nil {
249 return nil, nil, err
250 }
251 patch, err := pd.MakePatch(ts.Raw)
252 if err != nil {
253 return nil, nil, err
254 }
255 return patch, ts, nil
256 }
257
258
259 func (pd *PsDigest) MakePatch(sig []byte) (*binpatch.PatchSet, error) {
260 si, ok := psStyles[pd.SigStyle]
261 if !ok {
262 return nil, errors.New("invalid powershell signature style")
263 }
264 var buf bytes.Buffer
265 buf.WriteString("\r\n" + si.start + psBegin + si.end + "\r\n")
266 b64 := base64.StdEncoding.EncodeToString(sig)
267 for i := 0; i < len(b64); i += 64 {
268 j := i + 64
269 if j > len(b64) {
270 j = len(b64)
271 }
272 buf.WriteString(si.start + b64[i:j] + si.end + "\r\n")
273 }
274 buf.WriteString(si.start + psEnd + si.end + "\r\n")
275 patch := binpatch.New()
276 var encoded []byte
277 if pd.IsUtf16 {
278 encoded = []byte(toUtf16(buf.String()))
279 } else {
280 encoded = buf.Bytes()
281 }
282 patch.Add(pd.TextSize, int64(pd.SigSize), encoded)
283 return patch, nil
284 }
285
286 func readLine(br *bufio.Reader, isUtf16 bool) (string, error) {
287 line, err := br.ReadString('\n')
288 if isUtf16 && err == nil {
289
290 var zero byte
291 zero, err = br.ReadByte()
292 if zero != 0 {
293 return "", errors.New("malformed utf16")
294 }
295 line += "\x00"
296 }
297 return line, err
298 }
299
300
301 func toUtf16(x string) string {
302 runes := utf16.Encode([]rune(x))
303 buf := bytes.NewBuffer(make([]byte, 0, 2*len(runes)))
304 binary.Write(buf, binary.LittleEndian, runes)
305 return buf.String()
306 }
307
308
309 func writeUtf16(d io.Writer, x string, isUtf16 bool) error {
310 if isUtf16 {
311 _, err := d.Write([]byte(x))
312 return err
313 }
314 runes := utf16.Encode([]rune(x))
315 return binary.Write(d, binary.LittleEndian, runes)
316 }
317
318
319 func fromUtf16(x string) string {
320 runes := make([]uint16, len(x)/2)
321 binary.Read(bytes.NewReader([]byte(x)), binary.LittleEndian, runes)
322 return string(utf16.Decode(runes))
323 }
324
View as plain text