/// <summary> /// Locks/Unlocks an OpenXML document. /// </summary> /// <param name="args"></param> static void Main(string[] args) { if (args.Length != 2) { Console.WriteLine("Usage: lockdoc lock|unlock filename.docx"); return; } bool isLock = false; if (args[0].Equals("lock", StringComparison.OrdinalIgnoreCase)) { isLock = true; } else if (!args[0].Equals("unlock", StringComparison.OrdinalIgnoreCase)) { Console.Error.WriteLine("Wrong action!"); return; } WordprocessingDocument doc = WordprocessingDocument.Open(args[1], true); doc.ExtendedFilePropertiesPart.Properties.DocumentSecurity = new DocumentFormat.OpenXml.ExtendedProperties.DocumentSecurity (isLock ? "8" : "0"); doc.ExtendedFilePropertiesPart.Properties.Save(); DocumentProtection dp = doc.MainDocumentPart.DocumentSettingsPart .Settings.ChildElements.First <DocumentProtection>(); if (dp != null) { dp.Remove(); } if (isLock) { dp = new DocumentProtection(); dp.Edit = DocumentProtectionValues.Comments; dp.Enforcement = DocumentFormat.OpenXml.Wordprocessing.BooleanValues.One; doc.MainDocumentPart.DocumentSettingsPart.Settings.AppendChild(dp); } doc.MainDocumentPart.DocumentSettingsPart.Settings.Save(); doc.Close(); }
/* * Called from main function for every existing file * which reads the given .docx files * and replaces the contents as required. */ public void ReadDocx(string files) { // Separate Directory and file name (without extension name) string fileDir = Path.GetDirectoryName(files); string fileName = Path.GetFileNameWithoutExtension(files); string verify_full_fileDir = fileDir + "\\" + Path.GetFileName(files); List <bool> bodyMatch = new List <bool>(); List <bool> headerMatch = new List <bool>(); List <bool> footerMatch = new List <bool>(); List <string> tempList = new List <string>(); foreach (DirectoryModel row in SelectedDocs) { // If we should modify the document if (row.ApplyChanges) { tempList.Add(row.DirectoryNames); } } // If current file is in list of to be modified documents if (tempList.Any(verify_full_fileDir.Contains)) { byte[] byteArray = File.ReadAllBytes(files); using (MemoryStream stream = new MemoryStream()) { stream.Write(byteArray, 0, (int)byteArray.Length); using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(stream, true)) { DocumentProtection dp = wordDoc.MainDocumentPart.DocumentSettingsPart.Settings.GetFirstChild <DocumentProtection>(); if (dp != null && dp.Enforcement == DocumentFormat.OpenXml.OnOffValue.FromBoolean(true)) { dp.Remove(); // Doc is protected } foreach (var change in Changes) { if (HelperFunctions.AssertNotEmptyText(change.OldText, change.NewText)) { bodyMatch.Add(ReplaceText(wordDoc, change.OldText, change.NewText)); } } foreach (var headerChange in HeaderChanges) { if (HelperFunctions.AssertNotEmptyText(headerChange.OldText, headerChange.NewText)) { headerMatch.Add(ReplaceHeader(wordDoc, headerChange.OldText, headerChange.NewText)); } } foreach (var footerChange in FooterChanges) { if (HelperFunctions.AssertNotEmptyText(footerChange.OldText, footerChange.NewText)) { footerMatch.Add(ReplaceFooter(wordDoc, footerChange.OldText, footerChange.NewText)); } } if (Metadata.Count > 0) { UpdateMetadata(wordDoc); } } bool hasBodyMatch = bodyMatch.Any(x => x); bool hasHeaderMatch = headerMatch.Any(x => x); bool hasFooterMatch = footerMatch.Any(x => x); if (hasBodyMatch || hasHeaderMatch || hasFooterMatch) { // List of files that are processed. FileList.Add(fileName); // Check if that file has a date at the end string regexTest = @"_[0-9]{2}-[0-9]{2}-[0-9]{4}"; //check for _DD-MM-YYYY // If the file name contains date, it will take it out and replace for a new one, if not does nothing fileName = Regex.Replace(fileName, regexTest, ""); fileName += "_" + CurrentDate + ".docx"; // Add new date with file etensions // New file directory and old one string new_fileDir = fileDir + "\\" + fileName; string full_fileDir = fileDir + "\\" + Path.GetFileName(files); // Replace the old by the new string newPath = files.Replace(full_fileDir, new_fileDir); DirectoryList.Add(full_fileDir); // Append to directory list File.WriteAllBytes(newPath, stream.ToArray()); Logger = new LogFile(full_fileDir, Changes, hasBodyMatch, hasHeaderMatch, hasFooterMatch, HeaderChanges, FooterChanges); Logger.CreateLogFile(); HasChangedAnything.Add(true); } else { HasChangedAnything.Add(false); } } } }
// Main implementation private static void ApplyDocumentProtection(WordprocessingDocument wdDocument, string strPassword) { // Generate the Salt byte[] arrSalt = new byte[16]; RandomNumberGenerator rand = new RNGCryptoServiceProvider(); rand.GetNonZeroBytes(arrSalt); //Array to hold Key Values byte[] generatedKey = new byte[4]; //Maximum length of the password is 15 chars. int intMaxPasswordLength = 15; if (!String.IsNullOrEmpty(strPassword)) { // Truncate the password to 15 characters strPassword = strPassword.Substring(0, Math.Min(strPassword.Length, intMaxPasswordLength)); // Construct a new NULL-terminated string consisting of single-byte characters: // -- > Get the single-byte values by iterating through the Unicode characters of the truncated Password. // --> For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte. byte[] arrByteChars = new byte[strPassword.Length]; for (int intLoop = 0; intLoop < strPassword.Length; intLoop++) { int intTemp = Convert.ToInt32(strPassword[intLoop]); arrByteChars[intLoop] = Convert.ToByte(intTemp & 0x00FF); if (arrByteChars[intLoop] == 0) { arrByteChars[intLoop] = Convert.ToByte((intTemp & 0xFF00) >> 8); } } // Compute the high-order word of the new key: // --> Initialize from the initial code array (see below), depending on the strPassword’s length. int intHighOrderWord = InitialCodeArray[arrByteChars.Length - 1]; // --> For each character in the strPassword: // --> For every bit in the character, starting with the least significant and progressing to (but excluding) // the most significant, if the bit is set, XOR the key’s high-order word with the corresponding word from // the Encryption Matrix for (int intLoop = 0; intLoop < arrByteChars.Length; intLoop++) { int tmp = intMaxPasswordLength - arrByteChars.Length + intLoop; for (int intBit = 0; intBit < 7; intBit++) { if ((arrByteChars[intLoop] & (0x0001 << intBit)) != 0) { intHighOrderWord ^= EncryptionMatrix[tmp, intBit]; } } } // Compute the low-order word of the new key: // Initialize with 0 int intLowOrderWord = 0; // For each character in the strPassword, going backwards for (int intLoopChar = arrByteChars.Length - 1; intLoopChar >= 0; intLoopChar--) { // low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character intLowOrderWord = (((intLowOrderWord >> 14) & 0x0001) | ((intLowOrderWord << 1) & 0x7FFF)) ^ arrByteChars[intLoopChar]; } // Lastly,low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR strPassword length XOR 0xCE4B. intLowOrderWord = (((intLowOrderWord >> 14) & 0x0001) | ((intLowOrderWord << 1) & 0x7FFF)) ^ arrByteChars.Length ^ 0xCE4B; // Combine the Low and High Order Word int intCombinedkey = (intHighOrderWord << 16) + intLowOrderWord; // The byte order of the result shall be reversed [Example: 0x64CEED7E becomes 7EEDCE64. end example], // and that value shall be hashed as defined by the attribute values. for (int intTemp = 0; intTemp < 4; intTemp++) { generatedKey[intTemp] = Convert.ToByte(((uint)(intCombinedkey & (0x000000FF << (intTemp * 8)))) >> (intTemp * 8)); } } // Implementation Notes List: // --> In this third stage, the reversed byte order legacy hash from the second stage shall be converted to Unicode hex // --> string representation StringBuilder sb = new StringBuilder(); for (int intTemp = 0; intTemp < 4; intTemp++) { sb.Append(Convert.ToString(generatedKey[intTemp], 16)); } generatedKey = Encoding.Unicode.GetBytes(sb.ToString().ToUpper()); // Implementation Notes List: //Word appends the binary form of the salt attribute and not the base64 string representation when hashing // Before calculating the initial hash, you are supposed to prepend (not append) the salt to the key byte[] tmpArray1 = generatedKey; byte[] tmpArray2 = arrSalt; byte[] tempKey = new byte[tmpArray1.Length + tmpArray2.Length]; Buffer.BlockCopy(tmpArray2, 0, tempKey, 0, tmpArray2.Length); Buffer.BlockCopy(tmpArray1, 0, tempKey, tmpArray2.Length, tmpArray1.Length); generatedKey = tempKey; // Iterations specifies the number of times the hashing function shall be iteratively run (using each // iteration's result as the input for the next iteration). int iterations = 50000; // Implementation Notes List: //Word requires that the initial hash of the password with the salt not be considered in the count. // The initial hash of salt + key is not included in the iteration count. HashAlgorithm sha1 = new SHA1Managed(); generatedKey = sha1.ComputeHash(generatedKey); byte[] iterator = new byte[4]; for (int intTmp = 0; intTmp < iterations; intTmp++) { //When iterating on the hash, you are supposed to append the current iteration number. iterator[0] = Convert.ToByte((intTmp & 0x000000FF) >> 0); iterator[1] = Convert.ToByte((intTmp & 0x0000FF00) >> 8); iterator[2] = Convert.ToByte((intTmp & 0x00FF0000) >> 16); iterator[3] = Convert.ToByte((intTmp & 0xFF000000) >> 24); generatedKey = concatByteArrays(iterator, generatedKey); generatedKey = sha1.ComputeHash(generatedKey); } // Apply the element DocumentProtection documentProtection = new DocumentProtection(); documentProtection.Edit = DocumentProtectionValues.ReadOnly; OnOffValue docProtection = new OnOffValue(true); documentProtection.Enforcement = docProtection; documentProtection.CryptographicAlgorithmClass = CryptAlgorithmClassValues.Hash; documentProtection.CryptographicProviderType = CryptProviderValues.RsaFull; documentProtection.CryptographicAlgorithmType = CryptAlgorithmValues.TypeAny; documentProtection.CryptographicAlgorithmSid = 4; // SHA1 // The iteration count is unsigned UInt32Value uintVal = new UInt32Value(); uintVal.Value = (uint)iterations; documentProtection.CryptographicSpinCount = uintVal; documentProtection.Hash = Convert.ToBase64String(generatedKey); documentProtection.Salt = Convert.ToBase64String(arrSalt); // if protection in current document is exist then delete it DocumentProtection existingDocumentProtection = wdDocument.MainDocumentPart.DocumentSettingsPart.Settings.Descendants <DocumentProtection>().FirstOrDefault(); if (existingDocumentProtection != null) { existingDocumentProtection.Remove(); } wdDocument.MainDocumentPart.DocumentSettingsPart.Settings.AppendChild(documentProtection); wdDocument.MainDocumentPart.DocumentSettingsPart.Settings.Save(); // add permission to edit int id = 1; Body body = wdDocument.MainDocumentPart.Document.GetFirstChild <Body>(); // for all text foreach (var text in body.Descendants <Text>()) { PermStart newPermStart = new PermStart(); newPermStart.Id = id; newPermStart.EditorGroup = RangePermissionEditingGroupValues.Everyone; text.InsertBeforeSelf(newPermStart); PermEnd newPermEnd = new PermEnd(); newPermEnd.Id = id++; text.InsertAfterSelf(newPermEnd); } // for all table // var paras = body.Elements<Table>(); // // foreach (var para in paras) // { // PermStart newPermStart = new PermStart(); // newPermStart.Id = id; // newPermStart.EditorGroup = RangePermissionEditingGroupValues.Everyone; // para.InsertBeforeSelf(newPermStart); // PermEnd newPermEnd = new PermEnd(); // newPermEnd.Id = id++; // para.InsertAfterSelf(newPermEnd); // } wdDocument.MainDocumentPart.Document.Save(); // for headers foreach (HeaderPart headerPart in wdDocument.MainDocumentPart.HeaderParts) { foreach (var text in headerPart.RootElement.Descendants <Text>()) { PermStart newPermStart = new PermStart(); newPermStart.Id = id; newPermStart.EditorGroup = RangePermissionEditingGroupValues.Everyone; text.InsertBeforeSelf(newPermStart); PermEnd newPermEnd = new PermEnd(); newPermEnd.Id = id++; text.InsertAfterSelf(newPermEnd); } headerPart.Header.Save(); } foreach (FooterPart footerPart in wdDocument.MainDocumentPart.FooterParts) { foreach (var text in footerPart.RootElement.Descendants <Text>()) { PermStart newPermStart = new PermStart(); newPermStart.Id = id; newPermStart.EditorGroup = RangePermissionEditingGroupValues.Everyone; text.InsertBeforeSelf(newPermStart); PermEnd newPermEnd = new PermEnd(); newPermEnd.Id = id++; text.InsertAfterSelf(newPermEnd); } footerPart.Footer.Save(); } }