Skip to content

Commit

Permalink
Implement hash validation when downloading.
Browse files Browse the repository at this point in the history
Unfortunately this only validates at the very end - after all the
data would have been written to the client stream (except the final
chunk) but it's still better than nothing.

Tests will come tomorrow - tracked in googleapis#650.

Fixes googleapis#395 as well as we can for now.
  • Loading branch information
jskeet committed Dec 13, 2016
1 parent 806a0e5 commit aaa2aa0
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Apis.Download;
using Google.Apis.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;

namespace Google.Cloud.Storage.V1
{
/// <summary>
/// Subclass of <see cref="MediaDownloader"/> which validates the data it receives
/// against a CRC32c hash set in the header.
/// </summary>
internal sealed class HashValidatingDownloader : MediaDownloader
{
private string crc32cHashBase64;
private Crc32c hasher;

/// <summary>Constructs a new downloader with the given client service.</summary>
internal HashValidatingDownloader(IClientService service) : base(service)
{
}

protected override void OnResponseReceived(HttpResponseMessage response)
{
base.OnResponseReceived(response);

crc32cHashBase64 = null;
hasher = null;

IEnumerable<string> values;
if (response.Headers.TryGetValues(Crc32c.HashHeaderName, out values))
{
string prefix = Crc32c.HashName + "=";
foreach (var value in values.SelectMany(v => v.Split(',')))
{
if (value.StartsWith(prefix))
{
hasher = new Crc32c();
crc32cHashBase64 = value.Substring(prefix.Length);
break;
}
}
}
}

protected override void OnDataReceived(byte[] data, int length)
{
base.OnDataReceived(data, length);
hasher?.UpdateHash(data, 0, length);
}

protected override void OnDownloadCompleted()
{
base.OnDownloadCompleted();

if (crc32cHashBase64 != null)
{
string actualHash = Convert.ToBase64String(hasher.GetHash());
if (actualHash != crc32cHashBase64)
{
throw new IOException($"Incorrect hash: expected '{crc32cHashBase64}' (base64), was '{actualHash}' (base64)");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private void DownloadObjectImpl(
{
// URI will definitely not be null; that's constructed internally.
GaxPreconditions.CheckNotNull(destination, nameof(destination));
var downloader = new MediaDownloader(Service);
var downloader = new HashValidatingDownloader(Service);
options?.ModifyDownloader(downloader);
string uri = options == null ? baseUri : options.GetUri(baseUri);
if (progress != null)
Expand All @@ -126,7 +126,7 @@ private async Task DownloadObjectAsyncImpl(
IProgress<IDownloadProgress> progress)
{
GaxPreconditions.CheckNotNull(destination, nameof(destination));
var downloader = new MediaDownloader(Service);
var downloader = new HashValidatingDownloader(Service);
options?.ModifyDownloader(downloader);
string uri = options == null ? baseUri : options.GetUri(baseUri);
if (progress != null)
Expand Down

0 comments on commit aaa2aa0

Please sign in to comment.