/******************************************************************************* * 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 { /// /// The code signature properties of the project /// 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().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); } /// /// The certificate to sign the VBA project. /// /// 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). /// /// public X509Certificate2 Certificate { get; set; } /// /// The verifier /// 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; } } }