Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.EPPlus/4.0.3/EPPlus-4.0.3/VBA/ExcelVbaSignature.cs @ 18140

Last change on this file since 18140 was 12729, checked in by ascheibe, 9 years ago

#2399 r12463, r12464, r12465, r12470, r12471, r12472, r12474, r12728

File size: 15.6 KB
Line 
1/*******************************************************************************
2 * You may amend and distribute as you like, but don't remove this header!
3 *
4 * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
5 * See http://www.codeplex.com/EPPlus for details.
6 *
7 * Copyright (C) 2011  Jan Källman
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
17 * See the GNU Lesser General Public License for more details.
18 *
19 * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
20 * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
21 *
22 * All code and executables are provided "as is" with no warranty either express or implied.
23 * The author accepts no liability for any damage or loss of business that this product may cause.
24 *
25 * Code change notes:
26 *
27 * Author             Change            Date
28 *******************************************************************************
29 * Jan Källman    Added   26-MAR-2012
30 *******************************************************************************/
31using System;
32using System.Collections.Generic;
33using System.Linq;
34using System.Text;
35using System.Security.Cryptography.X509Certificates;
36using System.Security.Cryptography.Pkcs;
37using OfficeOpenXml.Utils;
38using System.IO;
39
40namespace OfficeOpenXml.VBA
41{
42    /// <summary>
43    /// The code signature properties of the project
44    /// </summary>
45    public class ExcelVbaSignature
46    {
47        const string schemaRelVbaSignature = "http://schemas.microsoft.com/office/2006/relationships/vbaProjectSignature";
48        Packaging.ZipPackagePart _vbaPart = null;
49        internal ExcelVbaSignature(Packaging.ZipPackagePart vbaPart)
50        {
51            _vbaPart = vbaPart;
52            GetSignature();
53        }
54        private void GetSignature()
55        {
56            if (_vbaPart == null) return;
57            var rel = _vbaPart.GetRelationshipsByType(schemaRelVbaSignature).FirstOrDefault();
58            if (rel != null)
59            {
60                Uri = UriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri);
61                Part = _vbaPart.Package.GetPart(Uri);
62
63                var stream = Part.GetStream();
64                BinaryReader br = new BinaryReader(stream);
65                uint cbSignature = br.ReadUInt32();       
66                uint signatureOffset = br.ReadUInt32();     //44 ??
67                uint cbSigningCertStore = br.ReadUInt32(); 
68                uint certStoreOffset = br.ReadUInt32();     
69                uint cbProjectName = br.ReadUInt32();       
70                uint projectNameOffset = br.ReadUInt32();   
71                uint fTimestamp = br.ReadUInt32();
72                uint cbTimestampUrl = br.ReadUInt32();
73                uint timestampUrlOffset = br.ReadUInt32(); 
74                byte[] signature = br.ReadBytes((int)cbSignature);
75                uint version = br.ReadUInt32();
76                uint fileType = br.ReadUInt32();
77
78                uint id = br.ReadUInt32();
79                while (id != 0)
80                {
81                    uint encodingType = br.ReadUInt32();
82                    uint length = br.ReadUInt32();
83                    if (length > 0)
84                    {
85                        byte[] value = br.ReadBytes((int)length);
86                        switch (id)
87                        {
88                            //Add property values here...
89                            case 0x20:
90                                Certificate = new X509Certificate2(value);
91                                break;
92                            default:
93                                break;
94                        }
95                    }
96                    id = br.ReadUInt32();
97                }
98                uint endel1 = br.ReadUInt32();  //0
99                uint endel2 = br.ReadUInt32();  //0
100                ushort rgchProjectNameBuffer = br.ReadUInt16();
101                ushort rgchTimestampBuffer = br.ReadUInt16();
102                Verifier = new SignedCms();
103                Verifier.Decode(signature);
104            }
105            else
106            {
107                Certificate = null;
108                Verifier = null;
109            }
110        }
111        //Create Oid from a bytearray
112        //private string ReadHash(byte[] content)
113        //{
114        //    StringBuilder builder = new StringBuilder();
115        //    int offset = 0x6;
116        //    if (0 < (content.Length))
117        //    {
118        //        byte num = content[offset];
119        //        byte num2 = (byte)(num / 40);
120        //        builder.Append(num2.ToString(null, null));
121        //        builder.Append(".");
122        //        num2 = (byte)(num % 40);
123        //        builder.Append(num2.ToString(null, null));
124        //        ulong num3 = 0L;
125        //        for (int i = offset + 1; i < content.Length; i++)
126        //        {
127        //            num2 = content[i];
128        //            num3 = (ulong)(ulong)(num3 << 7) + ((byte)(num2 & 0x7f));
129        //            if ((num2 & 0x80) == 0)
130        //            {
131        //                builder.Append(".");
132        //                builder.Append(num3.ToString(null, null));
133        //                num3 = 0L;
134        //            }
135        //            //1.2.840.113549.2.5
136        //        }
137        //    }
138
139
140        //    string oId = builder.ToString();
141
142        //    return oId;
143        //}
144        internal void Save(ExcelVbaProject proj)
145        {
146            if (Certificate == null)
147            {
148                return;
149            }
150           
151            if (Certificate.HasPrivateKey==false)    //No signature. Remove any Signature part
152            {
153                var storeCert = GetCertFromStore(StoreLocation.CurrentUser);
154                if (storeCert == null)
155                {
156                    storeCert = GetCertFromStore(StoreLocation.LocalMachine);
157                }
158                if (storeCert != null && storeCert.HasPrivateKey == true)
159                {
160                    Certificate = storeCert;
161                }
162                else
163                {
164                    foreach (var r in Part.GetRelationships())
165                    {
166                        Part.DeleteRelationship(r.Id);
167                    }
168                    Part.Package.DeletePart(Part.Uri);
169                    return;
170                }
171            }
172            var ms = new MemoryStream();
173            var bw = new BinaryWriter(ms);
174
175            byte[] certStore = GetCertStore();
176
177            byte[] cert = SignProject(proj);
178            bw.Write((uint)cert.Length);
179            bw.Write((uint)44);                  //?? 36 ref inside cert ??
180            bw.Write((uint)certStore.Length);    //cbSigningCertStore
181            bw.Write((uint)(cert.Length + 44));  //certStoreOffset
182            bw.Write((uint)0);                   //cbProjectName
183            bw.Write((uint)(cert.Length + certStore.Length + 44));    //projectNameOffset
184            bw.Write((uint)0);    //fTimestamp
185            bw.Write((uint)0);    //cbTimestampUrl
186            bw.Write((uint)(cert.Length + certStore.Length + 44 + 2));    //timestampUrlOffset
187            bw.Write(cert);
188            bw.Write(certStore);
189            bw.Write((ushort)0);//rgchProjectNameBuffer
190            bw.Write((ushort)0);//rgchTimestampBuffer
191            bw.Write((ushort)0);
192            bw.Flush();
193
194            var rel = proj.Part.GetRelationshipsByType(schemaRelVbaSignature).FirstOrDefault();
195            if (Part == null)
196            {
197
198                if (rel != null)
199                {
200                    Uri = rel.TargetUri;
201                    Part = proj._pck.GetPart(rel.TargetUri);
202                }
203                else
204                {
205                    Uri = new Uri("/xl/vbaProjectSignature.bin", UriKind.Relative);
206                    Part = proj._pck.CreatePart(Uri, ExcelPackage.schemaVBASignature);
207                }
208            }
209            if (rel == null)
210            {
211                proj.Part.CreateRelationship(UriHelper.ResolvePartUri(proj.Uri, Uri), Packaging.TargetMode.Internal, schemaRelVbaSignature);               
212            }
213            var b = ms.ToArray();
214            Part.GetStream(FileMode.Create).Write(b, 0, b.Length);           
215        }
216
217        private X509Certificate2 GetCertFromStore(StoreLocation loc)
218        {
219            try
220            {
221                X509Store store = new X509Store(loc);
222                store.Open(OpenFlags.ReadOnly);
223                try
224                {
225                    var storeCert = store.Certificates.Find(
226                                    X509FindType.FindByThumbprint,
227                                    Certificate.Thumbprint,
228                                    true
229                                    ).OfType<X509Certificate2>().FirstOrDefault();
230                    return storeCert;
231                }
232                finally
233                {
234                    store.Close();
235                }
236            }
237            catch
238            {
239                return null;
240            }
241        }
242
243        private byte[] GetCertStore()
244        {
245            var ms = new MemoryStream();
246            var bw = new BinaryWriter(ms);
247
248            bw.Write((uint)0); //Version
249            bw.Write((uint)0x54524543); //fileType
250
251            //SerializedCertificateEntry
252            var certData = Certificate.RawData;
253            bw.Write((uint)0x20);
254            bw.Write((uint)1);
255            bw.Write((uint)certData.Length);
256            bw.Write(certData);
257
258            //EndElementMarkerEntry
259            bw.Write((uint)0);
260            bw.Write((ulong)0);
261
262            bw.Flush();
263            return ms.ToArray();
264        }
265
266        private void WriteProp(BinaryWriter bw, int id, byte[] data)
267        {
268            bw.Write((uint)id);
269            bw.Write((uint)1);
270            bw.Write((uint)data.Length);
271            bw.Write(data);
272        }
273        internal byte[] SignProject(ExcelVbaProject proj)
274        {
275            if (!Certificate.HasPrivateKey)
276            {
277                //throw (new InvalidOperationException("The certificate doesn't have a private key"));
278                Certificate = null;
279                return null;
280            }
281            var hash = GetContentHash(proj);
282
283            BinaryWriter bw = new BinaryWriter(new MemoryStream());
284            bw.Write((byte)0x30); //Constructed Type
285            bw.Write((byte)0x32); //Total length
286            bw.Write((byte)0x30); //Constructed Type
287            bw.Write((byte)0x0E); //Length SpcIndirectDataContent
288            bw.Write((byte)0x06); //Oid Tag Indentifier
289            bw.Write((byte)0x0A); //Lenght OId
290            bw.Write(new byte[] { 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x1D }); //Encoded Oid 1.3.6.1.4.1.311.2.1.29
291            bw.Write((byte)0x04);   //Octet String Tag Identifier
292            bw.Write((byte)0x00);   //Zero length
293
294            bw.Write((byte)0x30); //Constructed Type (DigestInfo)
295            bw.Write((byte)0x20); //Length DigestInfo
296            bw.Write((byte)0x30); //Constructed Type (Algorithm)
297            bw.Write((byte)0x0C); //length AlgorithmIdentifier
298            bw.Write((byte)0x06); //Oid Tag Indentifier
299            bw.Write((byte)0x08); //Lenght OId
300            bw.Write(new byte[] { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05 }); //Encoded Oid for 1.2.840.113549.2.5 (AlgorithmIdentifier MD5)
301            bw.Write((byte)0x05);   //Null type identifier
302            bw.Write((byte)0x00);   //Null length
303            bw.Write((byte)0x04);   //Octet String Identifier
304            bw.Write((byte)hash.Length);   //Hash length
305            bw.Write(hash);                //Content hash
306
307            ContentInfo contentInfo = new ContentInfo(((MemoryStream)bw.BaseStream).ToArray());
308            contentInfo.ContentType.Value = "1.3.6.1.4.1.311.2.1.4";
309            Verifier = new SignedCms(contentInfo);
310            var signer = new CmsSigner(Certificate);
311            Verifier.ComputeSignature(signer, false);
312            return Verifier.Encode();
313        }
314
315        private byte[] GetContentHash(ExcelVbaProject proj)
316        {
317            //MS-OVBA 2.4.2
318            var enc = System.Text.Encoding.GetEncoding(proj.CodePage);
319            BinaryWriter bw = new BinaryWriter(new MemoryStream());
320            bw.Write(enc.GetBytes(proj.Name));
321            bw.Write(enc.GetBytes(proj.Constants));
322            foreach (var reference in proj.References)
323            {
324                if (reference.ReferenceRecordID == 0x0D)
325                {
326                    bw.Write((byte)0x7B);
327                }
328                if (reference.ReferenceRecordID == 0x0E)
329                {
330                    //var r = (ExcelVbaReferenceProject)reference;
331                    //BinaryWriter bwTemp = new BinaryWriter(new MemoryStream());
332                    //bwTemp.Write((uint)r.Libid.Length);
333                    //bwTemp.Write(enc.GetBytes(r.Libid));             
334                    //bwTemp.Write((uint)r.LibIdRelative.Length);
335                    //bwTemp.Write(enc.GetBytes(r.LibIdRelative));
336                    //bwTemp.Write(r.MajorVersion);
337                    //bwTemp.Write(r.MinorVersion);
338                    foreach (byte b in BitConverter.GetBytes((uint)reference.Libid.Length))  //Length will never be an UInt with 4 bytes that aren't 0 (> 0x00FFFFFF), so no need for the rest of the properties.
339                    {
340                        if (b != 0)
341                        {
342                            bw.Write(b);
343                        }
344                        else
345                        {
346                            break;
347                        }
348                    }
349                }
350            }
351            foreach (var module in proj.Modules)
352            {
353                var lines = module.Code.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
354                foreach (var line in lines)
355                {
356                    if (!line.StartsWith("attribute", true, null))
357                    {
358                        bw.Write(enc.GetBytes(line));
359                    }
360                }
361            }
362            var buffer = (bw.BaseStream as MemoryStream).ToArray();
363            var hp = System.Security.Cryptography.MD5CryptoServiceProvider.Create();
364            return hp.ComputeHash(buffer);
365        }
366        /// <summary>
367        /// The certificate to sign the VBA project.
368        /// <remarks>
369        /// This certificate must have a private key.
370        /// There is no validation that the certificate is valid for codesigning, so make sure it's valid to sign Excel files (Excel 2010 is more strict that prior versions).
371        /// </remarks>
372        /// </summary>
373        public X509Certificate2 Certificate { get; set; }
374        /// <summary>
375        /// The verifier
376        /// </summary>
377        public SignedCms Verifier { get; internal set; }
378#if !MONO
379        internal CompoundDocument Signature { get; set; }
380#endif
381        internal Packaging.ZipPackagePart Part { get; set; }
382        internal Uri Uri { get; private set; }
383    }
384}
Note: See TracBrowser for help on using the repository browser.