Merge pull request #11101 from chrisroberts/e-checksums

Extend checksum support for box validation and remote files
This commit is contained in:
Chris Roberts 2019-10-08 13:16:33 -07:00 committed by GitHub
commit 0d2751686b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 236 additions and 113 deletions

View File

@ -527,22 +527,11 @@ module Vagrant
end
def validate_checksum(checksum_type, checksum, path)
checksum_klass = case checksum_type.to_sym
when :md5
Digest::MD5
when :sha1
Digest::SHA1
when :sha256
Digest::SHA2
else
raise Errors::BoxChecksumInvalidType,
type: checksum_type.to_s
end
@logger.info("Validating checksum with #{checksum_klass}")
@logger.info("Validating checksum with #{checksum_type}")
@logger.info("Expected checksum: #{checksum}")
actual = FileChecksum.new(path, checksum_klass).checksum
actual = FileChecksum.new(path, checksum_type).checksum
@logger.info("Actual checksum: #{actual}")
if actual.casecmp(checksum) != 0
raise Errors::BoxChecksumMismatch,
actual: actual,

View File

@ -1,12 +1,14 @@
require "uri"
require "log4r"
require "digest"
require "digest/md5"
require "digest/sha1"
require "vagrant/util/busy"
require "vagrant/util/platform"
require "vagrant/util/subprocess"
require "vagrant/util/curl_helper"
require "vagrant/util/file_checksum"
module Vagrant
module Util
@ -20,12 +22,6 @@ module Vagrant
# Vagrant/1.7.4 (+https://www.vagrantup.com; ruby2.1.0)
USER_AGENT = "Vagrant/#{VERSION} (+https://www.vagrantup.com; #{RUBY_ENGINE}#{RUBY_VERSION}) #{ENV['VAGRANT_USER_AGENT_PROVISIONAL_STRING']}".freeze
# Supported file checksum
CHECKSUM_MAP = {
:md5 => Digest::MD5,
:sha1 => Digest::SHA1
}.freeze
# Hosts that do not require notification on redirect
SILENCED_HOSTS = [
"vagrantcloud.com".freeze,
@ -68,8 +64,11 @@ module Vagrant
@location_trusted = options[:location_trusted]
@checksums = {
:md5 => options[:md5],
:sha1 => options[:sha1]
}
:sha1 => options[:sha1],
:sha256 => options[:sha256],
:sha384 => options[:sha384],
:sha512 => options[:sha512]
}.compact
end
# This executes the actual download, downloading the source file
@ -165,36 +164,23 @@ module Vagrant
# @option checksums [String] :sha1 Compare SHA1 checksum
# @return [Boolean]
def validate_download!(source, path, checksums)
CHECKSUM_MAP.each do |type, klass|
if checksums[type]
result = checksum_file(klass, path)
checksums.each do |type, expected|
actual = FileChecksum.new(path, type).checksum
@logger.debug("Validating checksum (#{type}) for #{source}. " \
"expected: #{checksums[type]} actual: #{result}")
if checksums[type] != result
"expected: #{expected} actual: #{actual}")
if actual.casecmp(expected) != 0
raise Errors::DownloaderChecksumError.new(
source: source,
path: path,
type: type,
expected_checksum: checksums[type],
actual_checksum: result
expected_checksum: expected,
actual_checksum: actual
)
end
end
end
true
end
# Generate checksum on given file
#
# @param digest_class [Class] Digest class to use for generating checksum
# @param path [String, Pathname] Path to file
# @return [String] hexdigest result
def checksum_file(digest_class, path)
digester = digest_class.new
digester.file(path)
digester.hexdigest
end
def execute_curl(options, subprocess_options, &data_proc)
options = options.dup
options << subprocess_options

View File

@ -10,12 +10,26 @@ end
class FileChecksum
BUFFER_SIZE = 1024 * 8
# Supported file checksum
CHECKSUM_MAP = {
:md5 => Digest::MD5,
:sha1 => Digest::SHA1,
:sha256 => Digest::SHA256,
:sha384 => Digest::SHA384,
:sha512 => Digest::SHA512
}.freeze
# Initializes an object to calculate the checksum of a file. The given
# ``digest_klass`` should implement the ``DigestClass`` interface. Note
# that the built-in Ruby digest classes duck type this properly:
# Digest::MD5, Digest::SHA1, etc.
def initialize(path, digest_klass)
if digest_klass.is_a?(Class)
@digest_klass = digest_klass
else
@digest_klass = load_digest(digest_klass)
end
@path = path
end
@ -40,6 +54,17 @@ class FileChecksum
end
end
return digest.hexdigest
digest.hexdigest
end
private
def load_digest(type)
digest = CHECKSUM_MAP[type.to_s.to_sym]
if digest.nil?
raise Errors::BoxChecksumInvalidType,
type: type.to_s
end
digest
end
end

View File

@ -19,6 +19,12 @@ module VagrantPlugins
o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u|
options[:username] = u
end
o.on("-c", "--checksum CHECKSUM_VALUE", String, "Checksum of the box for this provider. --checksum-type option is required.") do |c|
options[:checksum] = c
end
o.on("-C", "--checksum-type TYPE", String, "Type of checksum used (md5, sha1, sha256, sha384, sha512). --checksum option is required.") do |c|
options[:checksum_type] = c
end
end
# Parse the options
@ -52,12 +58,13 @@ module VagrantPlugins
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
cloud_version = VagrantCloud::Version.new(box, version, nil, nil, access_token)
provider = VagrantCloud::Provider.new(cloud_version, provider_name, nil, url, org, box_name, access_token)
provider = VagrantCloud::Provider.new(cloud_version, provider_name, nil, url, org, box_name,
access_token, nil, options[:checksum], options[:checksum_type])
begin
success = provider.create_provider
@env.ui.success(I18n.t("cloud_command.provider.create_success", provider: provider_name, org: org, box_name: box_name, version: version))
success = success.delete_if{|_, v|v.nil?}
success = success.compact
VagrantPlugins::CloudCommand::Util.format_box_results(success, @env)
return 0
rescue VagrantCloud::ClientError => e

View File

@ -19,6 +19,12 @@ module VagrantPlugins
o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u|
options[:username] = u
end
o.on("-c", "--checksum CHECKSUM_VALUE", String, "Checksum of the box for this provider. --checksum-type option is required.") do |c|
options[:checksum] = c
end
o.on("-C", "--checksum-type TYPE", String, "Type of checksum used (md5, sha1, sha256, sha384, sha512). --checksum option is required.") do |c|
options[:checksum_type] = c
end
end
# Parse the options
@ -52,7 +58,8 @@ module VagrantPlugins
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
cloud_version = VagrantCloud::Version.new(box, version, nil, nil, access_token)
provider = VagrantCloud::Provider.new(cloud_version, provider_name, nil, url, org, box_name, access_token)
provider = VagrantCloud::Provider.new(cloud_version, provider_name, nil, url, org, box_name,
access_token, nil, options[:checksum], options[:checksum_type])
begin
success = provider.update

View File

@ -43,6 +43,12 @@ module VagrantPlugins
o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u|
options[:username] = u
end
o.on("-c", "--checksum CHECKSUM_VALUE", String, "Checksum of the box for this provider. --checksum-type option is required.") do |c|
options[:checksum] = c
end
o.on("-C", "--checksum-type TYPE", String, "Type of checksum used (md5, sha1, sha256, sha384, sha512). --checksum option is required.") do |c|
options[:checksum_type] = c
end
end
# Parse the options
@ -97,7 +103,8 @@ module VagrantPlugins
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, options[:short_description], options[:description], access_token)
cloud_version = VagrantCloud::Version.new(box, version, nil, options[:version_description], access_token)
provider = VagrantCloud::Provider.new(cloud_version, provider_name, nil, options[:url], org, box_name, access_token)
provider = VagrantCloud::Provider.new(cloud_version, provider_name, nil, options[:url], org, box_name,
access_token, nil, options[:checksum], options[:checksum_type])
ui = Vagrant::UI::Prefixed.new(@env.ui, "cloud")

View File

@ -7,6 +7,9 @@ module VagrantPlugins
attr_accessor :path
attr_accessor :md5
attr_accessor :sha1
attr_accessor :sha256
attr_accessor :sha384
attr_accessor :sha512
attr_accessor :env
attr_accessor :upload_path
attr_accessor :args
@ -26,6 +29,9 @@ module VagrantPlugins
@path = UNSET_VALUE
@md5 = UNSET_VALUE
@sha1 = UNSET_VALUE
@sha256 = UNSET_VALUE
@sha384 = UNSET_VALUE
@sha512 = UNSET_VALUE
@env = UNSET_VALUE
@upload_path = UNSET_VALUE
@privileged = UNSET_VALUE
@ -45,6 +51,9 @@ module VagrantPlugins
@path = nil if @path == UNSET_VALUE
@md5 = nil if @md5 == UNSET_VALUE
@sha1 = nil if @sha1 == UNSET_VALUE
@sha256 = nil if @sha256 == UNSET_VALUE
@sha384 = nil if @sha384 == UNSET_VALUE
@sha512 = nil if @sha512 == UNSET_VALUE
@env = {} if @env == UNSET_VALUE
@upload_path = "/tmp/vagrant-shell" if @upload_path == UNSET_VALUE
@privileged = true if @privileged == UNSET_VALUE

View File

@ -253,7 +253,10 @@ module VagrantPlugins
config.path,
download_path,
md5: config.md5,
sha1: config.sha1
sha1: config.sha1,
sha256: config.sha256,
sha384: config.sha384,
sha512: config.sha512
).download!
ext = File.extname(config.path)
script = download_path.read

View File

@ -48,7 +48,7 @@ describe VagrantPlugins::CloudCommand::ProviderCommand::Command::Create do
it "creates a provider" do
allow(VagrantCloud::Provider).to receive(:new).
with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token).
with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token, nil, nil, nil).
and_return(provider)
expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results)
@ -59,7 +59,7 @@ describe VagrantPlugins::CloudCommand::ProviderCommand::Command::Create do
it "displays an error if encoutering a problem with the request" do
allow(VagrantCloud::Provider).to receive(:new).
with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token).
with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token, nil, nil, nil).
and_return(provider)
allow(provider).to receive(:create_provider).
@ -73,7 +73,7 @@ describe VagrantPlugins::CloudCommand::ProviderCommand::Command::Create do
it "creates a provider" do
allow(VagrantCloud::Provider).to receive(:new).
with(version, "virtualbox", nil, "https://box.com/box", "vagrant", "box-name", client.token).
with(version, "virtualbox", nil, "https://box.com/box", "vagrant", "box-name", client.token, nil, nil, nil).
and_return(provider)
expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results)

View File

@ -48,7 +48,7 @@ describe VagrantPlugins::CloudCommand::ProviderCommand::Command::Update do
it "updates a provider" do
allow(VagrantCloud::Provider).to receive(:new).
with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token).
with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token, nil, nil, nil).
and_return(provider)
expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results)
@ -59,7 +59,7 @@ describe VagrantPlugins::CloudCommand::ProviderCommand::Command::Update do
it "displays an error if encoutering a problem with the request" do
allow(VagrantCloud::Provider).to receive(:new).
with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token).
with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token, nil, nil, nil).
and_return(provider)
allow(provider).to receive(:update).
@ -73,7 +73,7 @@ describe VagrantPlugins::CloudCommand::ProviderCommand::Command::Update do
it "creates a provider" do
allow(VagrantCloud::Provider).to receive(:new).
with(version, "virtualbox", nil, "https://box.com/box", "vagrant", "box-name", client.token).
with(version, "virtualbox", nil, "https://box.com/box", "vagrant", "box-name", client.token, nil, nil, nil).
and_return(provider)
expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results)

View File

@ -175,8 +175,16 @@ describe "Vagrant::Shell::Provisioner" do
end
context "with remote script" do
let(:filechecksum) { double("filechecksum", checksum: checksum_value) }
let(:checksum_value) { double("checksum_value") }
before do
allow(FileChecksum).to receive(:new).and_return(filechecksum)
allow_any_instance_of(Vagrant::Util::Downloader).to receive(:execute_curl).and_return(true)
end
context "that does not have matching sha1 checksum" do
let(:checksum_value) { "INVALID_VALUE" }
let(:config) {
double(
:config,
@ -188,18 +196,97 @@ describe "Vagrant::Shell::Provisioner" do
:binary => false,
:md5 => nil,
:sha1 => 'EXPECTED_VALUE',
:sha256 => nil,
:sha384 => nil,
:sha512 => nil,
:reset => false,
:reboot => false
)
}
let(:digest){ double("digest") }
before do
allow_any_instance_of(Vagrant::Util::Downloader).to receive(:execute_curl).and_return(true)
allow(digest).to receive(:file).and_return(digest)
expect(Digest::SHA1).to receive(:new).and_return(digest)
expect(digest).to receive(:hexdigest).and_return('INVALID_VALUE')
it "should raise an exception" do
vsp = VagrantPlugins::Shell::Provisioner.new(machine, config)
expect{ vsp.provision }.to raise_error(Vagrant::Errors::DownloaderChecksumError)
end
end
context "that does not have matching sha256 checksum" do
let(:checksum_value) { "INVALID_VALUE" }
let(:config) {
double(
:config,
:args => "doesn't matter",
:env => {},
:upload_path => "arbitrary",
:remote? => true,
:path => "http://example.com/script.sh",
:binary => false,
:md5 => nil,
:sha1 => nil,
:sha256 => 'EXPECTED_VALUE',
:sha384 => nil,
:sha512 => nil,
:reset => false,
:reboot => false
)
}
it "should raise an exception" do
vsp = VagrantPlugins::Shell::Provisioner.new(machine, config)
expect{ vsp.provision }.to raise_error(Vagrant::Errors::DownloaderChecksumError)
end
end
context "that does not have matching sha384 checksum" do
let(:checksum_value) { "INVALID_VALUE" }
let(:config) {
double(
:config,
:args => "doesn't matter",
:env => {},
:upload_path => "arbitrary",
:remote? => true,
:path => "http://example.com/script.sh",
:binary => false,
:md5 => nil,
:sha1 => nil,
:sha256 => nil,
:sha384 => 'EXPECTED_VALUE',
:sha512 => nil,
:reset => false,
:reboot => false
)
}
it "should raise an exception" do
vsp = VagrantPlugins::Shell::Provisioner.new(machine, config)
expect{ vsp.provision }.to raise_error(Vagrant::Errors::DownloaderChecksumError)
end
end
context "that does not have matching sha512 checksum" do
let(:checksum_value) { "INVALID_VALUE" }
let(:config) {
double(
:config,
:args => "doesn't matter",
:env => {},
:upload_path => "arbitrary",
:remote? => true,
:path => "http://example.com/script.sh",
:binary => false,
:md5 => nil,
:sha1 => nil,
:sha256 => nil,
:sha384 => nil,
:sha512 => 'EXPECTED_VALUE',
:reset => false,
:reboot => false
)
}
it "should raise an exception" do
vsp = VagrantPlugins::Shell::Provisioner.new(machine, config)
@ -209,6 +296,7 @@ describe "Vagrant::Shell::Provisioner" do
end
context "that does not have matching md5 checksum" do
let(:checksum_value) { "INVALID_VALUE" }
let(:config) {
double(
:config,
@ -220,19 +308,14 @@ describe "Vagrant::Shell::Provisioner" do
:binary => false,
:md5 => 'EXPECTED_VALUE',
:sha1 => nil,
:sha256 => nil,
:sha384 => nil,
:sha512 => nil,
:reset => false,
:reboot => false
)
}
let(:digest){ double("digest") }
before do
allow_any_instance_of(Vagrant::Util::Downloader).to receive(:execute_curl).and_return(true)
allow(digest).to receive(:file).and_return(digest)
expect(Digest::MD5).to receive(:new).and_return(digest)
expect(digest).to receive(:hexdigest).and_return('INVALID_VALUE')
end
it "should raise an exception" do
vsp = VagrantPlugins::Shell::Provisioner.new(machine, config)

View File

@ -175,23 +175,19 @@ describe Vagrant::Util::Downloader do
context "with checksum" do
let(:checksum_expected_value){ 'MD5_CHECKSUM_VALUE' }
let(:checksum_invalid_value){ 'INVALID_VALUE' }
let(:digest){ double("digest") }
let(:filechecksum) { double("filechecksum", checksum: checksum_value) }
let(:checksum_value) { double("checksum_value") }
before do
allow(digest).to receive(:file).and_return(digest)
end
before { allow(FileChecksum).to receive(:new).with(any_args).and_return(filechecksum) }
[Digest::MD5, Digest::SHA1].each do |klass|
[Digest::MD5, Digest::SHA1, Digest::SHA256, Digest::SHA384, Digest::SHA512].each do |klass|
short_name = klass.to_s.split("::").last.downcase
context "using #{short_name} digest" do
subject { described_class.new(source, destination, short_name.to_sym => checksum_expected_value) }
context "that matches expected value" do
before do
expect(klass).to receive(:new).and_return(digest)
expect(digest).to receive(:hexdigest).and_return(checksum_expected_value)
end
let(:checksum_value) { checksum_expected_value }
it "should not raise an exception" do
expect(subject.download!).to be(true)
@ -199,10 +195,7 @@ describe Vagrant::Util::Downloader do
end
context "that does not match expected value" do
before do
expect(klass).to receive(:new).and_return(digest)
expect(digest).to receive(:hexdigest).and_return(checksum_invalid_value)
end
let(:checksum_value) { checksum_invalid_value }
it "should raise an exception" do
expect{ subject.download! }.to raise_error(Vagrant::Errors::DownloaderChecksumError)
@ -213,13 +206,9 @@ describe Vagrant::Util::Downloader do
context "using both md5 and sha1 digests" do
context "that both match expected values" do
subject { described_class.new(source, destination, md5: checksum_expected_value, sha1: checksum_expected_value) }
let(:checksum_value) { checksum_expected_value }
before do
expect(Digest::MD5).to receive(:new).and_return(digest)
expect(Digest::SHA1).to receive(:new).and_return(digest)
expect(digest).to receive(:hexdigest).and_return(checksum_expected_value).exactly(2).times
end
subject { described_class.new(source, destination, md5: checksum_expected_value, sha1: checksum_expected_value) }
it "should not raise an exception" do
expect(subject.download!).to be(true)
@ -227,12 +216,14 @@ describe Vagrant::Util::Downloader do
end
context "that only sha1 matches expected value" do
subject { described_class.new(source, destination, md5: checksum_invalid_value, sha1: checksum_expected_value) }
subject { described_class.new(source, destination, md5: checksum_expected_value, sha1: checksum_expected_value) }
let(:valid_checksum) { double("valid_checksum", checksum: checksum_expected_value) }
let(:invalid_checksum) { double("invalid_checksum", checksum: checksum_invalid_value) }
before do
allow(Digest::MD5).to receive(:new).and_return(digest)
allow(Digest::SHA1).to receive(:new).and_return(digest)
expect(digest).to receive(:hexdigest).and_return(checksum_expected_value).at_least(:once)
allow(FileChecksum).to receive(:new).with(anything, :sha1).and_return(valid_checksum)
allow(FileChecksum).to receive(:new).with(anything, :md5).and_return(invalid_checksum)
end
it "should raise an exception" do
@ -241,12 +232,14 @@ describe Vagrant::Util::Downloader do
end
context "that only md5 matches expected value" do
subject { described_class.new(source, destination, md5: checksum_expected_value, sha1: checksum_invalid_value) }
subject { described_class.new(source, destination, md5: checksum_expected_value, sha1: checksum_expected_value) }
let(:valid_checksum) { double("valid_checksum", checksum: checksum_expected_value) }
let(:invalid_checksum) { double("invalid_checksum", checksum: checksum_invalid_value) }
before do
allow(Digest::MD5).to receive(:new).and_return(digest)
allow(Digest::SHA1).to receive(:new).and_return(digest)
expect(digest).to receive(:hexdigest).and_return(checksum_expected_value).at_least(:once)
allow(FileChecksum).to receive(:new).with(anything, :md5).and_return(valid_checksum)
allow(FileChecksum).to receive(:new).with(anything, :sha1).and_return(invalid_checksum)
end
it "should raise an exception" do
@ -255,14 +248,9 @@ describe Vagrant::Util::Downloader do
end
context "that none match expected value" do
let(:checksum_value) { checksum_expected_value }
subject { described_class.new(source, destination, md5: checksum_invalid_value, sha1: checksum_invalid_value) }
before do
allow(Digest::MD5).to receive(:new).and_return(digest)
allow(Digest::SHA1).to receive(:new).and_return(digest)
expect(digest).to receive(:hexdigest).and_return(checksum_expected_value).at_least(:once)
end
it "should raise an exception" do
expect{ subject.download! }.to raise_error(Vagrant::Errors::DownloaderChecksumError)
end

View File

@ -20,4 +20,16 @@ describe FileChecksum do
instance = described_class.new(file, Digest::SHA1)
expect(instance.checksum).to eq("264b207c7913e461c43d0f63d2512f4017af4755")
end
it "should support initialize with class or string" do
file = environment.workdir.join("file")
file.open("w+") { |f| f.write("HELLO!") }
%w(md5 sha1 sha256 sha384 sha512).each do |type|
klass = Digest.const_get(type.upcase)
t_i = described_class.new(file, type)
k_i = described_class.new(file, klass)
expect(t_i.checksum).to eq(k_i.checksum)
end
end
end

View File

@ -34,7 +34,7 @@ Gem::Specification.new do |s|
s.add_dependency "winrm", "~> 2.1"
s.add_dependency "winrm-fs", "~> 1.0"
s.add_dependency "winrm-elevated", "~> 1.1"
s.add_dependency "vagrant_cloud", "~> 2.0.2"
s.add_dependency "vagrant_cloud", "~> 2.0.3"
# NOTE: The ruby_dep gem is an implicit dependency from the listen gem. Later versions
# of the ruby_dep gem impose an aggressive constraint on the required ruby version (>= 2.2.5).

View File

@ -91,7 +91,8 @@ you are not using a catalog).
included within the catalog entry.
* `--checksum-type TYPE` - The type of checksum that `--checksum` is if it
is specified. Supported values are currently "md5", "sha1", and "sha256".
is specified. Supported values are currently "md5", "sha1", "sha256",
"sha384", and "sha512".
* `--name VALUE` - Logical name for the box. This is the value that you
would put into `config.vm.box` in your Vagrantfile. When adding a box from

View File

@ -83,6 +83,12 @@ The remainder of the available options are optional:
* `sha1` (string) - SHA1 checksum used to validate remotely downloaded shell files.
* `sha256` (string) - SHA256 checksum used to validate remotely downloaded shell files.
* `sha384` (string) - SHA384 checksum used to validate remotely downloaded shell files.
* `sha512` (string) - SHA512 checksum used to validate remotely downloaded shell files.
* `sensitive` (boolean) - Marks the Hash values used in the `env` option as sensitive
and hides them from output. By default this is "false".

View File

@ -46,7 +46,7 @@ when Vagrant must download the box. If this is specified, then
* `config.vm.box_download_checksum_type` (string) - The type of checksum specified
by `config.vm.box_download_checksum` (if any). Supported values are
currently "md5", "sha1", and "sha256".
currently "md5", "sha1", "sha256", "sha384", and "sha512".
* `config.vm.box_download_client_cert` (string) - Path to a client certificate to
use when downloading the box, if it is necessary. By default, no client