|  | /******************************************************************************* | 
|  | * You may amend and distribute as you like, but don't remove this header! | 
|  | * | 
|  | * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. | 
|  | * See http://www.codeplex.com/EPPlus for details. | 
|  | * | 
|  | * Copyright (C) 2011  Jan Källman | 
|  | * | 
|  | * This library is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU Lesser General Public | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2.1 of the License, or (at your option) any later version. | 
|  |  | 
|  | * This library is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | 
|  | * See the GNU Lesser General Public License for more details. | 
|  | * | 
|  | * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php | 
|  | * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html | 
|  | * | 
|  | * All code and executables are provided "as is" with no warranty either express or implied. | 
|  | * The author accepts no liability for any damage or loss of business that this product may cause. | 
|  | * | 
|  | * Code change notes: | 
|  | * | 
|  | * Author							Change						Date | 
|  | ******************************************************************************* | 
|  | * Jan Källman		Added		26-MAR-2012 | 
|  | *******************************************************************************/ | 
|  | using System; | 
|  | using System.Collections.Generic; | 
|  | using System.Linq; | 
|  | using System.Text; | 
|  | using System.Security.Cryptography.X509Certificates; | 
|  | using System.Security.Cryptography.Pkcs; | 
|  | using OfficeOpenXml.Utils; | 
|  | using System.IO; | 
|  |  | 
|  | namespace OfficeOpenXml.VBA | 
|  | { | 
|  | /// <summary> | 
|  | /// The code signature properties of the project | 
|  | /// </summary> | 
|  | public class ExcelVbaSignature | 
|  | { | 
|  | const string schemaRelVbaSignature = "http://schemas.microsoft.com/office/2006/relationships/vbaProjectSignature"; | 
|  | Packaging.ZipPackagePart _vbaPart = null; | 
|  | internal ExcelVbaSignature(Packaging.ZipPackagePart vbaPart) | 
|  | { | 
|  | _vbaPart = vbaPart; | 
|  | GetSignature(); | 
|  | } | 
|  | private void GetSignature() | 
|  | { | 
|  | if (_vbaPart == null) return; | 
|  | var rel = _vbaPart.GetRelationshipsByType(schemaRelVbaSignature).FirstOrDefault(); | 
|  | if (rel != null) | 
|  | { | 
|  | Uri = UriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri); | 
|  | Part = _vbaPart.Package.GetPart(Uri); | 
|  |  | 
|  | var stream = Part.GetStream(); | 
|  | BinaryReader br = new BinaryReader(stream); | 
|  | uint cbSignature = br.ReadUInt32(); | 
|  | uint signatureOffset = br.ReadUInt32();     //44 ?? | 
|  | uint cbSigningCertStore = br.ReadUInt32(); | 
|  | uint certStoreOffset = br.ReadUInt32(); | 
|  | uint cbProjectName = br.ReadUInt32(); | 
|  | uint projectNameOffset = br.ReadUInt32(); | 
|  | uint fTimestamp = br.ReadUInt32(); | 
|  | uint cbTimestampUrl = br.ReadUInt32(); | 
|  | uint timestampUrlOffset = br.ReadUInt32(); | 
|  | byte[] signature = br.ReadBytes((int)cbSignature); | 
|  | uint version = br.ReadUInt32(); | 
|  | uint fileType = br.ReadUInt32(); | 
|  |  | 
|  | uint id = br.ReadUInt32(); | 
|  | while (id != 0) | 
|  | { | 
|  | uint encodingType = br.ReadUInt32(); | 
|  | uint length = br.ReadUInt32(); | 
|  | if (length > 0) | 
|  | { | 
|  | byte[] value = br.ReadBytes((int)length); | 
|  | switch (id) | 
|  | { | 
|  | //Add property values here... | 
|  | case 0x20: | 
|  | Certificate = new X509Certificate2(value); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  | id = br.ReadUInt32(); | 
|  | } | 
|  | uint endel1 = br.ReadUInt32();  //0 | 
|  | uint endel2 = br.ReadUInt32();  //0 | 
|  | ushort rgchProjectNameBuffer = br.ReadUInt16(); | 
|  | ushort rgchTimestampBuffer = br.ReadUInt16(); | 
|  | Verifier = new SignedCms(); | 
|  | Verifier.Decode(signature); | 
|  | } | 
|  | else | 
|  | { | 
|  | Certificate = null; | 
|  | Verifier = null; | 
|  | } | 
|  | } | 
|  | //Create Oid from a bytearray | 
|  | //private string ReadHash(byte[] content) | 
|  | //{ | 
|  | //    StringBuilder builder = new StringBuilder(); | 
|  | //    int offset = 0x6; | 
|  | //    if (0 < (content.Length)) | 
|  | //    { | 
|  | //        byte num = content[offset]; | 
|  | //        byte num2 = (byte)(num / 40); | 
|  | //        builder.Append(num2.ToString(null, null)); | 
|  | //        builder.Append("."); | 
|  | //        num2 = (byte)(num % 40); | 
|  | //        builder.Append(num2.ToString(null, null)); | 
|  | //        ulong num3 = 0L; | 
|  | //        for (int i = offset + 1; i < content.Length; i++) | 
|  | //        { | 
|  | //            num2 = content[i]; | 
|  | //            num3 = (ulong)(ulong)(num3 << 7) + ((byte)(num2 & 0x7f)); | 
|  | //            if ((num2 & 0x80) == 0) | 
|  | //            { | 
|  | //                builder.Append("."); | 
|  | //                builder.Append(num3.ToString(null, null)); | 
|  | //                num3 = 0L; | 
|  | //            } | 
|  | //            //1.2.840.113549.2.5 | 
|  | //        } | 
|  | //    } | 
|  |  | 
|  |  | 
|  | //    string oId = builder.ToString(); | 
|  |  | 
|  | //    return oId; | 
|  | //} | 
|  | internal void Save(ExcelVbaProject proj) | 
|  | { | 
|  | if (Certificate == null) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (Certificate.HasPrivateKey==false)    //No signature. Remove any Signature part | 
|  | { | 
|  | var storeCert = GetCertFromStore(StoreLocation.CurrentUser); | 
|  | if (storeCert == null) | 
|  | { | 
|  | storeCert = GetCertFromStore(StoreLocation.LocalMachine); | 
|  | } | 
|  | if (storeCert != null && storeCert.HasPrivateKey == true) | 
|  | { | 
|  | Certificate = storeCert; | 
|  | } | 
|  | else | 
|  | { | 
|  | foreach (var r in Part.GetRelationships()) | 
|  | { | 
|  | Part.DeleteRelationship(r.Id); | 
|  | } | 
|  | Part.Package.DeletePart(Part.Uri); | 
|  | return; | 
|  | } | 
|  | } | 
|  | var ms = new MemoryStream(); | 
|  | var bw = new BinaryWriter(ms); | 
|  |  | 
|  | byte[] certStore = GetCertStore(); | 
|  |  | 
|  | byte[] cert = SignProject(proj); | 
|  | bw.Write((uint)cert.Length); | 
|  | bw.Write((uint)44);                  //?? 36 ref inside cert ?? | 
|  | bw.Write((uint)certStore.Length);    //cbSigningCertStore | 
|  | bw.Write((uint)(cert.Length + 44));  //certStoreOffset | 
|  | bw.Write((uint)0);                   //cbProjectName | 
|  | bw.Write((uint)(cert.Length + certStore.Length + 44));    //projectNameOffset | 
|  | bw.Write((uint)0);    //fTimestamp | 
|  | bw.Write((uint)0);    //cbTimestampUrl | 
|  | bw.Write((uint)(cert.Length + certStore.Length + 44 + 2));    //timestampUrlOffset | 
|  | bw.Write(cert); | 
|  | bw.Write(certStore); | 
|  | bw.Write((ushort)0);//rgchProjectNameBuffer | 
|  | bw.Write((ushort)0);//rgchTimestampBuffer | 
|  | bw.Write((ushort)0); | 
|  | bw.Flush(); | 
|  |  | 
|  | var rel = proj.Part.GetRelationshipsByType(schemaRelVbaSignature).FirstOrDefault(); | 
|  | if (Part == null) | 
|  | { | 
|  |  | 
|  | if (rel != null) | 
|  | { | 
|  | Uri = rel.TargetUri; | 
|  | Part = proj._pck.GetPart(rel.TargetUri); | 
|  | } | 
|  | else | 
|  | { | 
|  | Uri = new Uri("/xl/vbaProjectSignature.bin", UriKind.Relative); | 
|  | Part = proj._pck.CreatePart(Uri, ExcelPackage.schemaVBASignature); | 
|  | } | 
|  | } | 
|  | if (rel == null) | 
|  | { | 
|  | proj.Part.CreateRelationship(UriHelper.ResolvePartUri(proj.Uri, Uri), Packaging.TargetMode.Internal, schemaRelVbaSignature); | 
|  | } | 
|  | var b = ms.ToArray(); | 
|  | Part.GetStream(FileMode.Create).Write(b, 0, b.Length); | 
|  | } | 
|  |  | 
|  | private X509Certificate2 GetCertFromStore(StoreLocation loc) | 
|  | { | 
|  | try | 
|  | { | 
|  | X509Store store = new X509Store(loc); | 
|  | store.Open(OpenFlags.ReadOnly); | 
|  | try | 
|  | { | 
|  | var storeCert = store.Certificates.Find( | 
|  | X509FindType.FindByThumbprint, | 
|  | Certificate.Thumbprint, | 
|  | true | 
|  | ).OfType<X509Certificate2>().FirstOrDefault(); | 
|  | return storeCert; | 
|  | } | 
|  | finally | 
|  | { | 
|  | store.Close(); | 
|  | } | 
|  | } | 
|  | catch | 
|  | { | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | private byte[] GetCertStore() | 
|  | { | 
|  | var ms = new MemoryStream(); | 
|  | var bw = new BinaryWriter(ms); | 
|  |  | 
|  | bw.Write((uint)0); //Version | 
|  | bw.Write((uint)0x54524543); //fileType | 
|  |  | 
|  | //SerializedCertificateEntry | 
|  | var certData = Certificate.RawData; | 
|  | bw.Write((uint)0x20); | 
|  | bw.Write((uint)1); | 
|  | bw.Write((uint)certData.Length); | 
|  | bw.Write(certData); | 
|  |  | 
|  | //EndElementMarkerEntry | 
|  | bw.Write((uint)0); | 
|  | bw.Write((ulong)0); | 
|  |  | 
|  | bw.Flush(); | 
|  | return ms.ToArray(); | 
|  | } | 
|  |  | 
|  | private void WriteProp(BinaryWriter bw, int id, byte[] data) | 
|  | { | 
|  | bw.Write((uint)id); | 
|  | bw.Write((uint)1); | 
|  | bw.Write((uint)data.Length); | 
|  | bw.Write(data); | 
|  | } | 
|  | internal byte[] SignProject(ExcelVbaProject proj) | 
|  | { | 
|  | if (!Certificate.HasPrivateKey) | 
|  | { | 
|  | //throw (new InvalidOperationException("The certificate doesn't have a private key")); | 
|  | Certificate = null; | 
|  | return null; | 
|  | } | 
|  | var hash = GetContentHash(proj); | 
|  |  | 
|  | BinaryWriter bw = new BinaryWriter(new MemoryStream()); | 
|  | bw.Write((byte)0x30); //Constructed Type | 
|  | bw.Write((byte)0x32); //Total length | 
|  | bw.Write((byte)0x30); //Constructed Type | 
|  | bw.Write((byte)0x0E); //Length SpcIndirectDataContent | 
|  | bw.Write((byte)0x06); //Oid Tag Indentifier | 
|  | bw.Write((byte)0x0A); //Lenght OId | 
|  | 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 | 
|  | bw.Write((byte)0x04);   //Octet String Tag Identifier | 
|  | bw.Write((byte)0x00);   //Zero length | 
|  |  | 
|  | bw.Write((byte)0x30); //Constructed Type (DigestInfo) | 
|  | bw.Write((byte)0x20); //Length DigestInfo | 
|  | bw.Write((byte)0x30); //Constructed Type (Algorithm) | 
|  | bw.Write((byte)0x0C); //length AlgorithmIdentifier | 
|  | bw.Write((byte)0x06); //Oid Tag Indentifier | 
|  | bw.Write((byte)0x08); //Lenght OId | 
|  | bw.Write(new byte[] { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05 }); //Encoded Oid for 1.2.840.113549.2.5 (AlgorithmIdentifier MD5) | 
|  | bw.Write((byte)0x05);   //Null type identifier | 
|  | bw.Write((byte)0x00);   //Null length | 
|  | bw.Write((byte)0x04);   //Octet String Identifier | 
|  | bw.Write((byte)hash.Length);   //Hash length | 
|  | bw.Write(hash);                //Content hash | 
|  |  | 
|  | ContentInfo contentInfo = new ContentInfo(((MemoryStream)bw.BaseStream).ToArray()); | 
|  | contentInfo.ContentType.Value = "1.3.6.1.4.1.311.2.1.4"; | 
|  | Verifier = new SignedCms(contentInfo); | 
|  | var signer = new CmsSigner(Certificate); | 
|  | Verifier.ComputeSignature(signer, false); | 
|  | return Verifier.Encode(); | 
|  | } | 
|  |  | 
|  | private byte[] GetContentHash(ExcelVbaProject proj) | 
|  | { | 
|  | //MS-OVBA 2.4.2 | 
|  | var enc = System.Text.Encoding.GetEncoding(proj.CodePage); | 
|  | BinaryWriter bw = new BinaryWriter(new MemoryStream()); | 
|  | bw.Write(enc.GetBytes(proj.Name)); | 
|  | bw.Write(enc.GetBytes(proj.Constants)); | 
|  | foreach (var reference in proj.References) | 
|  | { | 
|  | if (reference.ReferenceRecordID == 0x0D) | 
|  | { | 
|  | bw.Write((byte)0x7B); | 
|  | } | 
|  | if (reference.ReferenceRecordID == 0x0E) | 
|  | { | 
|  | //var r = (ExcelVbaReferenceProject)reference; | 
|  | //BinaryWriter bwTemp = new BinaryWriter(new MemoryStream()); | 
|  | //bwTemp.Write((uint)r.Libid.Length); | 
|  | //bwTemp.Write(enc.GetBytes(r.Libid)); | 
|  | //bwTemp.Write((uint)r.LibIdRelative.Length); | 
|  | //bwTemp.Write(enc.GetBytes(r.LibIdRelative)); | 
|  | //bwTemp.Write(r.MajorVersion); | 
|  | //bwTemp.Write(r.MinorVersion); | 
|  | 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. | 
|  | { | 
|  | if (b != 0) | 
|  | { | 
|  | bw.Write(b); | 
|  | } | 
|  | else | 
|  | { | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | foreach (var module in proj.Modules) | 
|  | { | 
|  | var lines = module.Code.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); | 
|  | foreach (var line in lines) | 
|  | { | 
|  | if (!line.StartsWith("attribute", true, null)) | 
|  | { | 
|  | bw.Write(enc.GetBytes(line)); | 
|  | } | 
|  | } | 
|  | } | 
|  | var buffer = (bw.BaseStream as MemoryStream).ToArray(); | 
|  | var hp = System.Security.Cryptography.MD5CryptoServiceProvider.Create(); | 
|  | return hp.ComputeHash(buffer); | 
|  | } | 
|  | /// <summary> | 
|  | /// The certificate to sign the VBA project. | 
|  | /// <remarks> | 
|  | /// This certificate must have a private key. | 
|  | /// 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). | 
|  | /// </remarks> | 
|  | /// </summary> | 
|  | public X509Certificate2 Certificate { get; set; } | 
|  | /// <summary> | 
|  | /// The verifier | 
|  | /// </summary> | 
|  | public SignedCms Verifier { get; internal set; } | 
|  | #if !MONO | 
|  | internal CompoundDocument Signature { get; set; } | 
|  | #endif | 
|  | internal Packaging.ZipPackagePart Part { get; set; } | 
|  | internal Uri Uri { get; private set; } | 
|  | } | 
|  | } |