ValidateFooterChecksum() public method

Validates the footer checksum.
public ValidateFooterChecksum ( byte chunkCount, byte chunkOverallLength, byte ephemeralKey, int footerChecksumLength ) : void
chunkCount byte Number of chunks in the file.
chunkOverallLength byte Length of all chunks in the file.
ephemeralKey byte A 64 byte key.
footerChecksumLength int The length of the checksum.
return void
Ejemplo n.º 1
1
        /// <summary>
        /// Decrypts a file asynchron with libsodium and protobuf-net.
        /// </summary>
        /// <param name="recipientPrivateKey">A 32 byte private key.</param>
        /// <param name="inputFile">An encrypted file.</param>
        /// <param name="outputFolder">There the decrypted file will be stored.</param>
        /// <param name="decryptionProgress">StreamCryptorTaskAsyncProgress object.</param>
        /// <param name="overWrite">Overwrite the output file if it exist.</param>
        /// <param name="cancellationToken">Token to request task cancellation.</param>
        /// <returns>The fullpath to the decrypted file.</returns>
        /// <remarks>This method needs a revision.</remarks>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="FileNotFoundException"></exception>
        /// <exception cref="DirectoryNotFoundException"></exception>
        /// <exception cref="BadLastFileChunkException"></exception>
        /// <exception cref="BadFileChunkException"></exception>
        /// <exception cref="BadFileFooterException"></exception>
        /// <exception cref="BadFileHeaderException"></exception>
        /// <exception cref="IOException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="OperationCanceledException"></exception>
        public static async Task<string> DecryptFileWithStreamAsync(byte[] recipientPrivateKey, string inputFile, string outputFolder, IProgress<StreamCryptorTaskAsyncProgress> decryptionProgress = null, bool overWrite = false, CancellationToken cancellationToken = default(CancellationToken))
        {
            string outputFile = String.Empty;
            string outputFullPath = String.Empty;
            //used to check the file length of the unencrypted file, will be renamed to the outputFile (if the file is valid)
            string tmpFile = String.Empty;
            string tmpFullPath = String.Empty;
            try
            {
                //validate the recipientPrivateKey
                if (recipientPrivateKey == null || recipientPrivateKey.Length != ASYNC_KEY_LENGTH)
                {
                    throw new ArgumentOutOfRangeException("recipientPrivateKey", "invalid recipientPrivateKey");
                }
                //validate the inputFile
                if (string.IsNullOrEmpty(inputFile))
                {
                    throw new ArgumentOutOfRangeException("inputFile", (inputFile == null) ? 0 : inputFile.Length,
                      string.Format("inputFile must be greater {0} in length.", 0));
                }
                if (!File.Exists(inputFile))
                {
                    throw new FileNotFoundException("inputFile", "inputFile could not be found.");
                }
                //validate the outputFolder
                if (string.IsNullOrEmpty(outputFolder) || !Directory.Exists(outputFolder))
                {
                    throw new DirectoryNotFoundException("outputFolder must exist");
                }

                if (outputFolder.IndexOfAny(Path.GetInvalidPathChars()) > -1)
                    throw new ArgumentException("The given path to the output folder contains invalid characters!");

                //get a tmp name
                tmpFile = Utils.GetRandomFileName(MASKED_FILENAME_LENGTH, TEMP_FILE_EXTENSION);
                tmpFullPath = Path.Combine(outputFolder, tmpFile);
                using (FileStream fileStreamEncrypted = File.OpenRead(inputFile))
                {
                    //first read the file header
                    EncryptedFileHeader encryptedFileHeader = Serializer.DeserializeWithLengthPrefix<EncryptedFileHeader>(fileStreamEncrypted, PrefixStyle.Base128, 1);
                    if (encryptedFileHeader == null)
                    {
                        throw new BadFileHeaderException("Missing file header: maybe not a StreamCryptor encrypted file");
                    }
                    //decrypt the ephemeral key with our public box 
                    byte[] ephemeralKey = PublicKeyBox.Open(encryptedFileHeader.Key, encryptedFileHeader.EphemeralNonce, recipientPrivateKey, encryptedFileHeader.SenderPublicKey);
                    //validate our file header
                    encryptedFileHeader.ValidateHeaderChecksum(ephemeralKey, HEADER_CHECKSUM_LENGTH);
                    //check file header for compatibility
                    if ((encryptedFileHeader.Version >= MIN_VERSION) && (encryptedFileHeader.BaseNonce.Length == CHUNK_BASE_NONCE_LENGTH))
                    {
                        long overallChunkLength = 0;
                        long overallBytesRead = 0;
                        //restore the original file name
                        byte[] decryptedPaddedFileName = SecretBox.Open(encryptedFileHeader.Filename, encryptedFileHeader.FilenameNonce, Utils.GetEphemeralEncryptionKey(ephemeralKey));
                        //remove the padding
                        outputFile = Utils.PaddedByteArrayToString(decryptedPaddedFileName);
                        //check the decrypted outputFile name for invalid characters to prevent directory traversal
                        if (outputFile.IndexOfAny(Path.GetInvalidFileNameChars()) > -1)
                            throw new ArgumentException("The given name of the decrypted output filename contains invalid characters!");

                        outputFullPath = Path.Combine(outputFolder, outputFile);
                        //check for the correct result of Path.Combine
                        if (!outputFullPath.StartsWith(outputFolder))
                            throw new ArgumentException("The given output path seems to be bad!");

                        //keep the position for the footer
                        long fileStreamEncryptedPosition = 0;
                        int chunkNumber = CHUNK_COUNT_START;
                        //write the file to the tmpFullPath
                        using (FileStream fileStreamUnencrypted = File.OpenWrite(tmpFullPath))
                        {
                            //start reading the chunks
                            EncryptedFileChunk encryptedFileChunk = new EncryptedFileChunk();
                            while ((encryptedFileChunk = Serializer.DeserializeWithLengthPrefix<EncryptedFileChunk>(fileStreamEncrypted, PrefixStyle.Base128, 2)) != null)
                            {
                                //cancel the task if requested
                                cancellationToken.ThrowIfCancellationRequested();
                                //indicates if ChunkIsLast was found, to prepend more than one last chnunks.
                                bool isLastChunkFound = false;
                                byte[] chunkNonce = new byte[NONCE_LENGTH];
                                //check if this is the last chunk
                                if (encryptedFileChunk.ChunkIsLast)
                                {
                                    if (!isLastChunkFound)
                                    {
                                        //last
                                        chunkNonce = GetChunkNonce(encryptedFileHeader.BaseNonce, chunkNumber, true);
                                        isLastChunkFound = true;
                                    }
                                    else
                                    {
                                        throw new BadLastFileChunkException("there are more than one last chunk, file could be damaged or manipulated!");
                                    }
                                }
                                else
                                {
                                    //there will propably come more
                                    chunkNonce = GetChunkNonce(encryptedFileHeader.BaseNonce, chunkNumber, false);
                                }
                                //check the current chunk checksum
                                encryptedFileChunk.ValidateChunkChecksum(ephemeralKey, CHUNK_CHECKSUM_LENGTH);
                                byte[] decrypted = SecretBox.Open(encryptedFileChunk.Chunk, chunkNonce, Utils.GetEphemeralEncryptionKey(ephemeralKey));
                                await fileStreamUnencrypted.WriteAsync(decrypted, 0, decrypted.Length, cancellationToken).ConfigureAwait(false);
                                overallBytesRead += (long)decrypted.Length;
                                chunkNumber++;
                                overallChunkLength += encryptedFileChunk.ChunkLength;
                                fileStreamEncryptedPosition = fileStreamEncrypted.Position;
                                //report status
                                if (decryptionProgress != null)
                                {
                                    var args = new StreamCryptorTaskAsyncProgress();
                                    args.ProgressPercentage = (int)(encryptedFileHeader.UnencryptedFileLength <= 0 ? 0 : (100 * overallBytesRead) / encryptedFileHeader.UnencryptedFileLength);
                                    decryptionProgress.Report(args);
                                }
                            }
                        }
                        //set the last position
                        fileStreamEncrypted.Position = fileStreamEncryptedPosition;
                        //prepare the EncryptedFileFooter
                        EncryptedFileFooter encryptedFileFooter = new EncryptedFileFooter();
                        //get the file footer and validate him
                        encryptedFileFooter = Serializer.DeserializeWithLengthPrefix<EncryptedFileFooter>(fileStreamEncrypted, PrefixStyle.Base128, 3);
                        if (encryptedFileFooter == null)
                        {
                            throw new BadFileFooterException("Missing file footer: file could be damaged or manipulated!");
                        }
                        //validate the footer checksum
                        encryptedFileFooter.ValidateFooterChecksum(BitConverter.GetBytes(chunkNumber), BitConverter.GetBytes(overallChunkLength), ephemeralKey, FOOTER_CHECKSUM_LENGTH);
                    }
                    else
                    {
                        throw new BadFileHeaderException("Incompatible file header: maybe different library version!");
                    }
                    //check the produced output for the correct length
                    if (encryptedFileHeader.UnencryptedFileLength == new FileInfo(tmpFullPath).Length)
                    {
                        //check if the new output file already exists
                        if (File.Exists(outputFullPath))
                        {
                            if (!overWrite)
                            {
                                //we don`t overwrite the file
                                throw new IOException("Decrypted file aleary exits, won`t overwrite");
                            }
                            else
                            {
                                //just delete the output file, so we can write a new one
                                File.Delete(outputFullPath);
                            }
                        }
                        File.Move(tmpFullPath, outputFullPath);
                    }
                    else
                    {
                        //File is not valid (return null)
                        outputFile = null;
                        File.Delete(tmpFullPath);
                    }
                }
            }
            catch (AggregateException ex)
            {
                //delete the temp file
                File.Delete(tmpFullPath);
                //and throw the exception
                ExceptionDispatchInfo.Capture(ex).Throw();
            }
            catch (OperationCanceledException ex)
            {
                //delete the temp file
                File.Delete(tmpFullPath);
                //and throw the exception
                ExceptionDispatchInfo.Capture(ex).Throw();
            }
            return outputFile;
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Decrypts a file asynchron into memory with libsodium and protobuf-net.
        /// </summary>
        /// <param name="recipientPrivateKey">A 32 byte private key.</param>
        /// <param name="inputFile">An encrypted file.</param>
        /// <param name="decryptionProgress">StreamCryptorTaskAsyncProgress object.</param>
        /// <param name="cancellationToken">Token to request task cancellation.</param>
        /// <returns>A DecryptedFile object.</returns>
        /// <remarks>This method can throw an OutOfMemoryException when there is not enough ram to hold the DecryptedFile!</remarks>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="FileNotFoundException"></exception>
        /// <exception cref="BadLastFileChunkException"></exception>
        /// <exception cref="BadFileChunkException"></exception>
        /// <exception cref="BadFileFooterException"></exception>
        /// <exception cref="BadFileHeaderException"></exception>
        /// <exception cref="IOException"></exception>
        /// <exception cref="OutOfMemoryException"></exception>
        /// <exception cref="OperationCanceledException"></exception>
        public static async Task<DecryptedFile> DecryptFileWithStreamAsync(byte[] recipientPrivateKey, string inputFile, IProgress<StreamCryptorTaskAsyncProgress> decryptionProgress = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            DecryptedFile decryptedFile = new DecryptedFile();
            try
            {
                //validate the recipientPrivateKey
                if (recipientPrivateKey == null || recipientPrivateKey.Length != ASYNC_KEY_LENGTH)
                {
                    throw new ArgumentOutOfRangeException("recipientPrivateKey", "invalid recipientPrivateKey");
                }
                //validate the inputFile
                if (string.IsNullOrEmpty(inputFile))
                {
                    throw new ArgumentOutOfRangeException("inputFile", (inputFile == null) ? 0 : inputFile.Length,
                      string.Format("inputFile must be greater {0} in length.", 0));
                }
                if (!File.Exists(inputFile))
                {
                    throw new FileNotFoundException("inputFile", "inputFile could not be found.");
                }

                using (FileStream fileStreamEncrypted = File.OpenRead(inputFile))
                {
                    //first read the file header
                    EncryptedFileHeader encryptedFileHeader = new EncryptedFileHeader();
                    encryptedFileHeader = Serializer.DeserializeWithLengthPrefix<EncryptedFileHeader>(fileStreamEncrypted, PrefixStyle.Base128, 1);
                    //decrypt the ephemeral key with our public box 
                    byte[] ephemeralKey = PublicKeyBox.Open(encryptedFileHeader.Key, encryptedFileHeader.EphemeralNonce, recipientPrivateKey, encryptedFileHeader.SenderPublicKey);
                    //validate our file header
                    encryptedFileHeader.ValidateHeaderChecksum(ephemeralKey, HEADER_CHECKSUM_LENGTH);
                    //check file header for compatibility
                    if ((encryptedFileHeader.Version >= MIN_VERSION) && (encryptedFileHeader.BaseNonce.Length == CHUNK_BASE_NONCE_LENGTH))
                    {
                        long overallChunkLength = 0;
                        long overallBytesRead = 0;
                        //restore the original file name
                        byte[] encryptedPaddedFileName = SecretBox.Open(encryptedFileHeader.Filename, encryptedFileHeader.FilenameNonce, Utils.GetEphemeralEncryptionKey(ephemeralKey));
                        //remove the padding
                        decryptedFile.FileName = Utils.PaddedByteArrayToString(encryptedPaddedFileName);
                        //keep the position for the footer
                        long fileStreamEncryptedPosition = 0;
                        int chunkNumber = CHUNK_COUNT_START;
                        //write the file to the tmpFullPath
                        using (MemoryStream fileStreamUnencrypted = new MemoryStream())
                        {
                            //start reading the chunks
                            EncryptedFileChunk encryptedFileChunk = new EncryptedFileChunk();
                            while ((encryptedFileChunk = Serializer.DeserializeWithLengthPrefix<EncryptedFileChunk>(fileStreamEncrypted, PrefixStyle.Base128, 2)) != null)
                            {
                                //cancel the task if requested
                                cancellationToken.ThrowIfCancellationRequested();
                                //indicates if ChunkIsLast was found, to prepend more than one last chnunks.
                                bool isLastChunkFound = false;
                                byte[] chunkNonce = new byte[NONCE_LENGTH];
                                //check if this is the last chunk
                                if (encryptedFileChunk.ChunkIsLast)
                                {
                                    if (!isLastChunkFound)
                                    {
                                        //last
                                        chunkNonce = GetChunkNonce(encryptedFileHeader.BaseNonce, chunkNumber, true);
                                        isLastChunkFound = true;
                                    }
                                    else
                                    {
                                        throw new BadLastFileChunkException("there are more than one last chunk, file could be damaged or manipulated!");
                                    }
                                }
                                else
                                {
                                    //there will propably come more
                                    chunkNonce = GetChunkNonce(encryptedFileHeader.BaseNonce, chunkNumber);
                                }
                                //check the current chunk checksum
                                encryptedFileChunk.ValidateChunkChecksum(ephemeralKey, CHUNK_CHECKSUM_LENGTH);
                                byte[] decrypted = SecretBox.Open(encryptedFileChunk.Chunk, chunkNonce, Utils.GetEphemeralEncryptionKey(ephemeralKey));
                                await fileStreamUnencrypted.WriteAsync(decrypted, 0, decrypted.Length, cancellationToken).ConfigureAwait(false);
                                overallBytesRead += (long)decrypted.Length;
                                chunkNumber++;
                                overallChunkLength += encryptedFileChunk.ChunkLength;
                                fileStreamEncryptedPosition = fileStreamEncrypted.Position;
                                //report status
                                if (decryptionProgress != null)
                                {
                                    var args = new StreamCryptorTaskAsyncProgress();
                                    args.ProgressPercentage = (int)(encryptedFileHeader.UnencryptedFileLength <= 0 ? 0 : (100 * overallBytesRead) / encryptedFileHeader.UnencryptedFileLength);
                                    decryptionProgress.Report(args);
                                }
                            }
                            decryptedFile.FileData = fileStreamUnencrypted.ToArray();
                            decryptedFile.FileSize = decryptedFile.FileData.Length;
                        }
                        //set the last position
                        fileStreamEncrypted.Position = fileStreamEncryptedPosition;
                        //prepare the EncryptedFileFooter
                        EncryptedFileFooter encryptedFileFooter = new EncryptedFileFooter();
                        //get the file footer and validate him
                        encryptedFileFooter = Serializer.DeserializeWithLengthPrefix<EncryptedFileFooter>(fileStreamEncrypted, PrefixStyle.Base128, 3);
                        if (encryptedFileFooter == null)
                        {
                            throw new BadFileFooterException("Missing file footer: file could be damaged or manipulated!");
                        }
                        //validate the footer checksum
                        encryptedFileFooter.ValidateFooterChecksum(BitConverter.GetBytes(chunkNumber), BitConverter.GetBytes(overallChunkLength), ephemeralKey, FOOTER_CHECKSUM_LENGTH);
                    }
                    else
                    {
                        throw new BadFileHeaderException("Incompatible file header: maybe different library version!");
                    }
                    //check the produced output for the correct length
                    if (encryptedFileHeader.UnencryptedFileLength != decryptedFile.FileSize)
                    {
                        //File is not valid (return null)
                        decryptedFile = null;
                    }
                }
            }
            catch (AggregateException ex)
            {
                //and throw the exception
                ExceptionDispatchInfo.Capture(ex).Throw();
            }
            return decryptedFile;
        }