/// <summary>
        /// Attempts to find an Inner Header and File Info object using the supplied decryption Keys.
        /// If the decryption keys do not decrypt the header, then they were not listed as a recipient.
        /// </summary>
        /// <param name="header">the ingested header object from the file</param>
        /// <param name="RecipientKeys">the Keys object with the key pair to attempt for decryption</param>
        /// <param name="Result">output: a FullHeader object with the fully decrypted file header details</param>
        /// <returns>false on any error, including (but not limited to): bad ciphertext, not a recipient</returns>
        private static bool TryDecryptHeader(HeaderInfo header, miniLockManaged.Keys RecipientKeys, out FullHeader Result)
        {
            Result = new FullHeader();
            Result.UpdateFromHeader(header);
            UTF8Encoding utf8 = new UTF8Encoding();

            foreach (string n in header.decryptInfo.Keys) // n is outer nonce
            {
                //System.Diagnostics.Debug.Print("decryptInfo Nonce: " + k);  // DON'T LEAK!!!
                string v      = header.decryptInfo[n]; //payload
                byte[] buffer = RecipientKeys.TryDecrypt(header.ephemeral, true, v.ToBytesFromBase64(), n.ToBytesFromBase64());
                if (buffer != null)                    // looks like we're a recipient!  proceed!  if not just move on to the next one
                {
                    string stuff = utf8.GetString(buffer);
                    buffer.Wipe();
                    InnerHeaderInfo ih = InnerHeaderInfo.FromJSON(stuff);
                    stuff = null;
                    if (ih != null)
                    {
                        //System.Diagnostics.Debug.Print("_IH OBJECT=" + ih.ToJSON());  // DON'T LEAK!!!
                        if (ih.recipientID != RecipientKeys.PublicID)
                        {
                            Result.Clear();
                            return(false);
                        }
                        if (!miniLockManaged.Keys.ValidatePublicKey(ih.senderID))
                        {
                            Result.Clear();
                            return(false);
                        }
                        Result.UpdateFromInnerHeader(ih);
                        buffer = ih.fileInfo.ToBytesFromBase64();
                        // use same nonce from OUTER
                        buffer = RecipientKeys.TryDecrypt(ih.senderID, buffer, n.ToBytesFromBase64());
                        if (buffer != null)
                        {
                            stuff = utf8.GetString(buffer);
                            buffer.Wipe();
                            FileInfo fi = FileInfo.FromJSON(stuff);
                            if (fi != null)
                            {
                                Result.UpdateFromFileInfo(fi);
                                //System.Diagnostics.Debug.Print("_FI OBJECT=" + fi.ToJSON()); // DON'T LEAK!!!
                                return(true);
                            }
                        }
                    }
                }
            }
            // either not a recipient, or something else went wrong
            Result.Clear();
            return(false);
        }
 public void SetRequestHeaders(string Headers)
 {
     if (Headers == null || Headers == String.Empty)
     {
         return;
     }
     string[] arrHeaders = Headers.Split('\r', '\n');
     foreach (string FullHeader in arrHeaders)
     {
         if (FullHeader != String.Empty)
         {
             int loc = FullHeader.IndexOf(':');
             if (loc < 0)
             {
                 RequestHeaders[FullHeader] = "";
             }
             else
             {
                 RequestHeaders[FullHeader.Substring(0, loc)] = FullHeader.Substring(loc + 1);
                 Debug.WriteLine(string.Format("Writing Header:{0}={1}", FullHeader.Substring(0, loc), FullHeader.Substring(loc + 1)));
             }
         }
     }
 }
        /// <summary>
        /// Decrypt a miniLock file using the specified Keys
        /// </summary>
        /// <param name="TheFile"></param>
        /// <param name="RecipientKeys"></param>
        /// <returns>null on any error, or a DecryptedFile object with the raw file contents, a plaintext hash,
        /// the SenderID, and the stored filename</returns>
        public static DecryptedFileDetails DecryptFile(FileStream SourceFile, string DestinationFileFullPath, bool OverWriteDestination, miniLockManaged.Keys RecipientKeys)
        {
            if (SourceFile == null)
            {
                throw new ArgumentNullException("SourceFile");
            }
            if (DestinationFileFullPath == null)
            {
                throw new ArgumentNullException("DestinationFile");
            }
            if (!SourceFile.CanRead)
            {
                throw new InvalidOperationException("Source File not readable!");
            }
            if (System.IO.File.Exists(DestinationFileFullPath) && !OverWriteDestination)
            {
                // be fault tolerant
                System.IO.FileInfo existing    = new System.IO.FileInfo(DestinationFileFullPath);
                string             newFilename = DestinationFileFullPath;
                int counter = 1;
                do
                {
                    newFilename  = DestinationFileFullPath.Replace(existing.Extension, "");
                    newFilename += '(' + counter++.ToString() + ')' + existing.Extension;
                } while (File.Exists(newFilename));
                DestinationFileFullPath = newFilename;
                // this is not fault tolerant
                //throw new InvalidOperationException("Destination File already exists!  Set OverWriteDestination true or choose a different filename.");
            }

            FullHeader fileStuff = new FullHeader();
            HeaderInfo h;

            byte[] buffer = null;

            // after this call, the source file pointer should be positioned to the end of the header
            int hLen = IngestHeader(ref SourceFile, out h);

            if (hLen < 0)
            {
                SourceFile.Close();
                SourceFile.Dispose();
                return(null);
            }
            hLen += 12;                                             // the 8 magic bytes and the 4 header length bytes and the length of the JSON header object
            long theCliff = SourceFile.Length - hLen;               // this is the ADJUSTED point where the file cursor falls off the cliff

            if (!TryDecryptHeader(h, RecipientKeys, out fileStuff)) // ciphertext hash is compared later
            {
                fileStuff.Clear();
                SourceFile.Close();
                SourceFile.Dispose();
                return(null);
            }

            Blake2sCSharp.Hasher b2sPlain  = Blake2sCSharp.Blake2S.Create(); // a nice-to-have for the user
            Blake2sCSharp.Hasher b2sCipher = Blake2sCSharp.Blake2S.Create(); // a check to make sure the ciphertext wasn't altered
            //note:  in theory, if the ciphertext doesn't decrypt at any point, there is likely something wrong with it up to and
            //  including truncation/extension
            //  BUT the hash is included in the header, and should be checked.

            DecryptedFileDetails results = new DecryptedFileDetails();

            results.ActualDecryptedFilePath = DestinationFileFullPath; // if the filename got changed, it happened before this point
            string tempFile = null;                                    // save the filename of the temp file so that the temp directory created with it is also killed

            System.IO.FileStream tempFS = GetTempFileStream(out tempFile);

            int    cursor      = 0;
            UInt64 chunkNumber = 0;

            byte[] chunkNonce = new byte[24];                                        // always a constant length
            Array.Copy(fileStuff.fileNonce, chunkNonce, fileStuff.fileNonce.Length); // copy it once and be done with it
            do
            {
                // how big is this chunk? (32bit number, little endien)
                buffer = new byte[4];
                if (SourceFile.Read(buffer, 0, buffer.Length) != buffer.Length)
                {
                    //read error
                    fileStuff.Clear();
                    SourceFile.Close();
                    SourceFile.Dispose();
                    TrashTempFileStream(tempFS, tempFile);
                    return(null);
                }
                b2sCipher.Update(buffer);  // have to include ALL the bytes, even the chunk-length bytes
                UInt32 chunkLength = Utilities.BytesToUInt32(buffer);
                if (chunkLength > MAX_CHUNK_SIZE)
                {
                    //something went wrong!
                    fileStuff.Clear();
                    SourceFile.Close();
                    SourceFile.Dispose();
                    TrashTempFileStream(tempFS, tempFile);
                    return(null);
                }
                cursor += 4; // move past the chunk length

                //the XSalsa20Poly1305 process, ALWAYS expands the plaintext by MacSizeInBytes
                // (authentication), so read the plaintext chunk length, add those bytes to the
                // value, then read that many bytes out of the ciphertext buffer
                byte[] chunk = new byte[chunkLength + XSalsa20Poly1305.MacSizeInBytes];
                //Array.Copy(buffer, cursor,
                //           chunk, 0,
                //           chunk.Length);
                if (SourceFile.Read(chunk, 0, chunk.Length) != chunk.Length)
                {
                    //read error
                    fileStuff.Clear();
                    SourceFile.Close();
                    SourceFile.Dispose();
                    TrashTempFileStream(tempFS, tempFile);
                    return(null);
                }
                b2sCipher.Update(chunk); // get hash of cipher text to compare to stored File Info Object
                cursor += chunk.Length;  // move the cursor past this chunk
                if (cursor >= theCliff)  // this is the last chunk
                {
                    // set most significant bit of nonce
                    chunkNonce[23] |= 0x80;
                }
                byte[] decryptBytes = XSalsa20Poly1305.TryDecrypt(chunk, fileStuff.fileKey, chunkNonce);
                if (decryptBytes == null)
                {
                    // nonce or key incorrect, or chunk has been altered (truncated?)
                    buffer = null;
                    fileStuff.Clear();
                    SourceFile.Close();
                    SourceFile.Dispose();
                    TrashTempFileStream(tempFS, tempFile);
                    return(null);
                }
                if (chunkNumber == 0) // first chunk is always filename '\0' padded
                {
                    results.StoredFilename = new UTF8Encoding().GetString(decryptBytes).Replace("\0", "").Trim();
                }
                else
                {
                    b2sPlain.Update(decryptBytes);                      // give the user a nice PlainText hash
                    tempFS.Write(decryptBytes, 0, decryptBytes.Length); // start building the output file
                }
                decryptBytes.Wipe();                                    // DON'T LEAK!!!
                // since the first chunkNonce is just the fileNonce and a bunch of 0x00's,
                //  it's safe to do the chunk number update as a post-process operation
                Utilities.UInt64ToBytes(++chunkNumber, chunkNonce, 16);
            } while (cursor < theCliff);
            SourceFile.Close();
            SourceFile.Dispose();
            byte[] ctActualHash = b2sCipher.Finish();
            if (!CryptoBytes.ConstantTimeEquals(ctActualHash, fileStuff.ciphertextHash))
            {
                // ciphertext was altered
                TrashTempFileStream(tempFS, tempFile);
                return(null);
            }
            results.SenderID = Keys.GetPublicIDFromKeyBytes(fileStuff.senderID);
            fileStuff.Clear(); // wipe the sensitive stuff!
            tempFS.Flush();
            tempFS.Close();
            tempFS.Dispose();
            //produce a handy hash for use by the end-user (not part of the spec)
            results.PlainTextBlake2sHash = b2sPlain.Finish().ToBase64String();

            System.IO.File.Move(tempFile, DestinationFileFullPath);
            // WARNING:  only use if the method that created the temp file also created a random subdir!
            Directory.Delete(new System.IO.FileInfo(tempFile).DirectoryName, true); // this is done since we didn't use TrashTempfileStream

            return(results);
        }
Exemple #4
0
        XmlWriter GetXml(byte[] SectionBytes, XmlWriter XW)
        {
            string SectionName = "";

            XW.WriteStartElement("section");

            string SectionString = Encoding.UTF8.GetString(SectionBytes);

            string[] SectionParts   = SectionString.Split(new string[] { "\r\n\r\n" }, 2, StringSplitOptions.None);
            string[] SectionHeaders = SectionParts[0].Split(new string[] { "\r\n" }, StringSplitOptions.None);

            int HeaderCount = 0;

            if (SectionHeaders.Length == 0)
            {
                XW.WriteStartElement("section_value");
                XW.WriteAttributeString("oname", "value");
                byte[] BinaryData = new byte[SectionBytes.Length - (SectionParts[0].Length + 4)];//#don't use len(parts[1]) as binary values would have incorrect length in the string form
                Array.Copy(SectionBytes, SectionParts[0].Length + 4, BinaryData, 0, BinaryData.Length);

                if (Tools.IsBinary(BinaryData))
                {
                    this.WriteValue(XW, BinaryData);
                    //XW.WriteAttributeString("encoded", "1");
                    //XW.WriteValue(Tools.Base64EncodeByteArray(BinaryData));
                }
                else
                {
                    this.WriteValue(XW, SectionParts[1]);
                    //if (SectionParts[1].Contains("\r\n"))
                    //{
                    //    XW.WriteAttributeString("crlf", "1");
                    //}
                    //XW.WriteValue(SectionParts[1]);
                }
                XW.WriteEndElement();//Closing the <section_value> tag
            }
            else
            {
                foreach (string FullHeader in SectionHeaders)
                {
                    HeaderCount++;

                    string[] HeaderParts     = FullHeader.Split(new string[] { ";" }, StringSplitOptions.None);
                    string[] HeaderFirstPart = HeaderParts[0].Split(new string[] { ":" }, StringSplitOptions.None);

                    List <string[]> HeaderPartNameValuePairs = new List <string[]>();

                    for (int i = 1; i < HeaderParts.Length; i++)
                    {
                        string[] KeyValuePair = HeaderParts[i].Split(new string[] { "=" }, StringSplitOptions.None);
                        KeyValuePair[0] = KeyValuePair[0].Trim();
                        KeyValuePair[1] = KeyValuePair[1].Trim();
                        if (KeyValuePair[1].StartsWith("\"") && KeyValuePair[1].EndsWith("\""))
                        {
                            KeyValuePair[1] = KeyValuePair[1].Trim('"');
                        }
                        else if (KeyValuePair[1].StartsWith("'") && KeyValuePair[1].EndsWith("'"))
                        {
                            KeyValuePair[1] = KeyValuePair[1].Trim('\'');
                        }
                        HeaderPartNameValuePairs.Add(KeyValuePair);
                    }
                    if (HeaderCount == 1)
                    {
                        XW.WriteStartElement("first_header");
                        XW.WriteAttributeString("header_name", HeaderFirstPart[0].Trim());
                        XW.WriteAttributeString("oname", Tools.UrlDecode(HeaderFirstPart[1].Trim()));

                        foreach (string[] KeyValuePair in HeaderPartNameValuePairs)
                        {
                            if (KeyValuePair[0] == "name")
                            {
                                SectionName = Tools.UrlDecode(KeyValuePair[1]);
                            }
                        }
                        if (SectionName.Length > 0)
                        {
                            XW.WriteAttributeString("name", SectionName);
                        }
                        foreach (string[] KeyValuePair in HeaderPartNameValuePairs)
                        {
                            if (KeyValuePair[0] != "name")
                            {
                                XW.WriteStartElement("first_header_section");
                                XW.WriteAttributeString("oname", Tools.UrlDecode(KeyValuePair[0]));
                                //XW.WriteValue(Tools.UrlDecode(KeyValuePair[1]));
                                this.WriteValue(XW, Tools.UrlDecode(KeyValuePair[1]));
                                XW.WriteEndElement();
                            }
                        }

                        XW.WriteStartElement("section_value");
                        if (SectionName.Length > 0)
                        {
                            XW.WriteAttributeString("oname", SectionName);
                        }
                        byte[] BinaryData = new byte[SectionBytes.Length - (SectionParts[0].Length + 4)];//#don't use len(parts[1]) as binary values would have incorrect length in the string form
                        Array.Copy(SectionBytes, SectionParts[0].Length + 4, BinaryData, 0, BinaryData.Length);

                        if (Tools.IsBinary(BinaryData))
                        {
                            this.WriteValue(XW, BinaryData);
                            //XW.WriteAttributeString("encoded", "1");
                            //XW.WriteValue(Tools.Base64EncodeByteArray(BinaryData));
                        }
                        else
                        {
                            this.WriteValue(XW, SectionParts[1]);
                            //if (SectionParts[1].Contains("\r\n"))
                            //{
                            //    XW.WriteAttributeString("crlf", "1");
                            //}
                            //XW.WriteValue(SectionParts[1]);
                        }
                        XW.WriteEndElement();//Closing the <section_value> tag
                    }
                    else
                    {
                        if (HeaderCount == 2)
                        {
                            XW.WriteStartElement("other_headers");
                            if (SectionName.Length > 0)
                            {
                                XW.WriteAttributeString("oname", SectionName);
                            }
                        }
                        XW.WriteStartElement("header");
                        XW.WriteAttributeString("oname", HeaderFirstPart[0].Trim());

                        XW.WriteStartElement("value");
                        //XW.WriteValue(HeaderFirstPart[1]);
                        this.WriteValue(XW, HeaderFirstPart[1]);
                        XW.WriteEndElement();//Closing the <value> tag
                        foreach (string[] KeyValuePair in HeaderPartNameValuePairs)
                        {
                            XW.WriteStartElement("header_section");
                            XW.WriteAttributeString("oname", Tools.UrlDecode(KeyValuePair[0]));
                            //XW.WriteValue(Tools.UrlDecode(KeyValuePair[1]));
                            this.WriteValue(XW, Tools.UrlDecode(KeyValuePair[1]));
                            XW.WriteEndElement(); //Closing the <header_section> tag
                        }
                        XW.WriteEndElement();     //Closing the <header> tag
                    }
                }
                if (HeaderCount > 1)
                {
                    XW.WriteEndElement();//Closing the <other_headers> tag
                }
                if (HeaderCount > 0)
                {
                    XW.WriteEndElement();//Closing the <first_header> tag
                }
            }

            XW.WriteEndElement();//Closing the <section> tag
            return(XW);
        }