mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
535 lines
14 KiB
Ruby
535 lines
14 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rubygems/test_case'
|
|
|
|
unless Gem::HAVE_OPENSSL
|
|
warn 'Skipping Gem::Security::Policy tests. openssl not found.'
|
|
end
|
|
|
|
class TestGemSecurityPolicy < Gem::TestCase
|
|
ALTERNATE_KEY = load_key 'alternate'
|
|
INVALID_KEY = load_key 'invalid'
|
|
CHILD_KEY = load_key 'child'
|
|
GRANDCHILD_KEY = load_key 'grandchild'
|
|
INVALIDCHILD_KEY = load_key 'invalidchild'
|
|
|
|
ALTERNATE_CERT = load_cert 'alternate'
|
|
CA_CERT = load_cert 'ca'
|
|
CHILD_CERT = load_cert 'child'
|
|
EXPIRED_CERT = load_cert 'expired'
|
|
FUTURE_CERT = load_cert 'future'
|
|
GRANDCHILD_CERT = load_cert 'grandchild'
|
|
INVALIDCHILD_CERT = load_cert 'invalidchild'
|
|
INVALID_ISSUER_CERT = load_cert 'invalid_issuer'
|
|
INVALID_SIGNER_CERT = load_cert 'invalid_signer'
|
|
WRONG_KEY_CERT = load_cert 'wrong_key'
|
|
|
|
def setup
|
|
super
|
|
|
|
@spec = quick_gem 'a' do |s|
|
|
s.description = 'π'
|
|
s.files = %w[lib/code.rb]
|
|
end
|
|
|
|
@digest = OpenSSL::Digest.new Gem::Security::DIGEST_NAME
|
|
@trust_dir = Gem::Security.trust_dir.dir # HACK use the object
|
|
|
|
@no = Gem::Security::NoSecurity
|
|
@almost_no = Gem::Security::AlmostNoSecurity
|
|
@low = Gem::Security::LowSecurity
|
|
@medium = Gem::Security::MediumSecurity
|
|
@high = Gem::Security::HighSecurity
|
|
|
|
@chain = Gem::Security::Policy.new(
|
|
'Chain',
|
|
:verify_data => true,
|
|
:verify_signer => true,
|
|
:verify_chain => true,
|
|
:verify_root => false,
|
|
:only_trusted => false,
|
|
:only_signed => false
|
|
)
|
|
|
|
@root = Gem::Security::Policy.new(
|
|
'Root',
|
|
:verify_data => true,
|
|
:verify_signer => true,
|
|
:verify_chain => true,
|
|
:verify_root => true,
|
|
:only_trusted => false,
|
|
:only_signed => false
|
|
)
|
|
end
|
|
|
|
def test_check_data
|
|
data = digest 'hello'
|
|
|
|
signature = sign data
|
|
|
|
assert @almost_no.check_data(PUBLIC_KEY, @digest, signature, data)
|
|
end
|
|
|
|
def test_check_data_invalid
|
|
data = digest 'hello'
|
|
|
|
signature = sign data
|
|
|
|
invalid = digest 'hello!'
|
|
|
|
e = assert_raise Gem::Security::Exception do
|
|
@almost_no.check_data PUBLIC_KEY, @digest, signature, invalid
|
|
end
|
|
|
|
assert_equal 'invalid signature', e.message
|
|
end
|
|
|
|
def test_check_chain
|
|
chain = [PUBLIC_CERT, CHILD_CERT, GRANDCHILD_CERT]
|
|
|
|
assert @chain.check_chain chain, Time.now
|
|
end
|
|
|
|
def test_check_chain_empty_chain
|
|
e = assert_raise Gem::Security::Exception do
|
|
@chain.check_chain [], Time.now
|
|
end
|
|
|
|
assert_equal 'empty signing chain', e.message
|
|
end
|
|
|
|
def test_check_chain_invalid
|
|
chain = [PUBLIC_CERT, CHILD_CERT, INVALIDCHILD_CERT]
|
|
|
|
e = assert_raise Gem::Security::Exception do
|
|
@chain.check_chain chain, Time.now
|
|
end
|
|
|
|
assert_equal "invalid signing chain: " +
|
|
"certificate #{INVALIDCHILD_CERT.subject} " +
|
|
"was not issued by #{CHILD_CERT.subject}", e.message
|
|
end
|
|
|
|
def test_check_chain_no_chain
|
|
e = assert_raise Gem::Security::Exception do
|
|
@chain.check_chain nil, Time.now
|
|
end
|
|
|
|
assert_equal 'missing signing chain', e.message
|
|
end
|
|
|
|
def test_check_cert
|
|
assert @low.check_cert(PUBLIC_CERT, nil, Time.now)
|
|
end
|
|
|
|
def test_check_cert_expired
|
|
e = assert_raise Gem::Security::Exception do
|
|
@low.check_cert EXPIRED_CERT, nil, Time.now
|
|
end
|
|
|
|
assert_equal "certificate #{EXPIRED_CERT.subject} " +
|
|
"not valid after #{EXPIRED_CERT.not_after}",
|
|
e.message
|
|
end
|
|
|
|
def test_check_cert_future
|
|
e = assert_raise Gem::Security::Exception do
|
|
@low.check_cert FUTURE_CERT, nil, Time.now
|
|
end
|
|
|
|
assert_equal "certificate #{FUTURE_CERT.subject} " +
|
|
"not valid before #{FUTURE_CERT.not_before}",
|
|
e.message
|
|
end
|
|
|
|
def test_check_cert_invalid_issuer
|
|
e = assert_raise Gem::Security::Exception do
|
|
@low.check_cert INVALID_ISSUER_CERT, PUBLIC_CERT, Time.now
|
|
end
|
|
|
|
assert_equal "certificate #{INVALID_ISSUER_CERT.subject} " +
|
|
"was not issued by #{PUBLIC_CERT.subject}",
|
|
e.message
|
|
end
|
|
|
|
def test_check_cert_issuer
|
|
assert @low.check_cert(CHILD_CERT, PUBLIC_CERT, Time.now)
|
|
end
|
|
|
|
def test_check_cert_no_signer
|
|
e = assert_raise Gem::Security::Exception do
|
|
@high.check_cert(nil, nil, Time.now)
|
|
end
|
|
|
|
assert_equal 'missing signing certificate', e.message
|
|
end
|
|
|
|
def test_check_key
|
|
assert @almost_no.check_key(PUBLIC_CERT, PRIVATE_KEY)
|
|
end
|
|
|
|
def test_check_key_no_signer
|
|
assert @almost_no.check_key(nil, nil)
|
|
|
|
e = assert_raise Gem::Security::Exception do
|
|
@high.check_key(nil, nil)
|
|
end
|
|
|
|
assert_equal 'missing key or signature', e.message
|
|
end
|
|
|
|
def test_check_key_wrong_key
|
|
e = assert_raise Gem::Security::Exception do
|
|
@almost_no.check_key(PUBLIC_CERT, ALTERNATE_KEY)
|
|
end
|
|
|
|
assert_equal "certificate #{PUBLIC_CERT.subject} " +
|
|
"does not match the signing key", e.message
|
|
end
|
|
|
|
def test_check_root
|
|
chain = [PUBLIC_CERT, CHILD_CERT, INVALIDCHILD_CERT]
|
|
|
|
assert @chain.check_root chain, Time.now
|
|
end
|
|
|
|
def test_check_root_empty_chain
|
|
e = assert_raise Gem::Security::Exception do
|
|
@chain.check_root [], Time.now
|
|
end
|
|
|
|
assert_equal 'missing root certificate', e.message
|
|
end
|
|
|
|
def test_check_root_invalid_signer
|
|
chain = [INVALID_SIGNER_CERT]
|
|
|
|
e = assert_raise Gem::Security::Exception do
|
|
@chain.check_root chain, Time.now
|
|
end
|
|
|
|
assert_equal "certificate #{INVALID_SIGNER_CERT.subject} " +
|
|
"was not issued by #{INVALID_SIGNER_CERT.issuer}",
|
|
e.message
|
|
end
|
|
|
|
def test_check_root_not_self_signed
|
|
chain = [INVALID_ISSUER_CERT]
|
|
|
|
e = assert_raise Gem::Security::Exception do
|
|
@chain.check_root chain, Time.now
|
|
end
|
|
|
|
assert_equal "root certificate #{INVALID_ISSUER_CERT.subject} " +
|
|
"is not self-signed (issuer #{INVALID_ISSUER_CERT.issuer})",
|
|
e.message
|
|
end
|
|
|
|
def test_check_root_no_chain
|
|
e = assert_raise Gem::Security::Exception do
|
|
@chain.check_root nil, Time.now
|
|
end
|
|
|
|
assert_equal 'missing signing chain', e.message
|
|
end
|
|
|
|
def test_check_trust
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
assert @high.check_trust [PUBLIC_CERT], @digest, @trust_dir
|
|
end
|
|
|
|
def test_check_trust_child
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
assert @high.check_trust [PUBLIC_CERT, CHILD_CERT], @digest, @trust_dir
|
|
end
|
|
|
|
def test_check_trust_empty_chain
|
|
e = assert_raise Gem::Security::Exception do
|
|
@chain.check_trust [], @digest, @trust_dir
|
|
end
|
|
|
|
assert_equal 'missing root certificate', e.message
|
|
end
|
|
|
|
def test_check_trust_mismatch
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
e = assert_raise Gem::Security::Exception do
|
|
@high.check_trust [WRONG_KEY_CERT], @digest, @trust_dir
|
|
end
|
|
|
|
assert_equal "trusted root certificate #{PUBLIC_CERT.subject} checksum " +
|
|
"does not match signing root certificate checksum", e.message
|
|
end
|
|
|
|
def test_check_trust_no_chain
|
|
e = assert_raise Gem::Security::Exception do
|
|
@chain.check_trust nil, @digest, @trust_dir
|
|
end
|
|
|
|
assert_equal 'missing signing chain', e.message
|
|
end
|
|
|
|
def test_check_trust_no_trust
|
|
e = assert_raise Gem::Security::Exception do
|
|
@high.check_trust [PUBLIC_CERT], @digest, @trust_dir
|
|
end
|
|
|
|
assert_equal "root cert #{PUBLIC_CERT.subject} is not trusted", e.message
|
|
end
|
|
|
|
def test_check_trust_no_trust_child
|
|
e = assert_raise Gem::Security::Exception do
|
|
@high.check_trust [PUBLIC_CERT, CHILD_CERT], @digest, @trust_dir
|
|
end
|
|
|
|
assert_equal "root cert #{PUBLIC_CERT.subject} is not trusted " +
|
|
"(root of signing cert #{CHILD_CERT.subject})", e.message
|
|
end
|
|
|
|
def test_subject
|
|
assert_equal 'email:nobody@example', @no.subject(PUBLIC_CERT)
|
|
assert_equal '/C=JP/ST=Tokyo/O=RubyGemsTest/CN=CA', @no.subject(CA_CERT)
|
|
end
|
|
|
|
def test_verify
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
assert @almost_no.verify [PUBLIC_CERT], nil, *dummy_signatures
|
|
end
|
|
|
|
def test_verify_chain_signatures
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
assert @high.verify [PUBLIC_CERT], nil, *dummy_signatures
|
|
end
|
|
|
|
def test_verify_chain_key
|
|
@almost_no.verify [PUBLIC_CERT], PRIVATE_KEY, *dummy_signatures
|
|
end
|
|
|
|
def test_verify_no_digests
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
_, signatures = dummy_signatures
|
|
|
|
e = assert_raise Gem::Security::Exception do
|
|
@almost_no.verify [PUBLIC_CERT], nil, {}, signatures
|
|
end
|
|
|
|
assert_equal 'no digests provided (probable bug)', e.message
|
|
end
|
|
|
|
def test_verify_no_digests_no_security
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
_, signatures = dummy_signatures
|
|
|
|
e = assert_raise Gem::Security::Exception do
|
|
@no.verify [PUBLIC_CERT], nil, {}, signatures
|
|
end
|
|
|
|
assert_equal 'missing digest for 0', e.message
|
|
end
|
|
|
|
def test_verify_no_signatures
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
digests, = dummy_signatures
|
|
|
|
use_ui @ui do
|
|
@no.verify [PUBLIC_CERT], nil, digests, {}, 'some_gem'
|
|
end
|
|
|
|
assert_match "WARNING: some_gem is not signed\n", @ui.error
|
|
|
|
assert_raise Gem::Security::Exception do
|
|
@high.verify [PUBLIC_CERT], nil, digests, {}
|
|
end
|
|
end
|
|
|
|
def test_verify_no_signatures_no_digests
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
use_ui @ui do
|
|
@no.verify [PUBLIC_CERT], nil, {}, {}, 'some_gem'
|
|
end
|
|
|
|
assert_empty @ui.output
|
|
assert_empty @ui.error
|
|
end
|
|
|
|
def test_verify_not_enough_signatures
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
digests, signatures = dummy_signatures
|
|
|
|
data = digest 'goodbye'
|
|
|
|
signatures[1] = PRIVATE_KEY.sign @digest.new, data.digest
|
|
|
|
e = assert_raise Gem::Security::Exception do
|
|
@almost_no.verify [PUBLIC_CERT], nil, digests, signatures
|
|
end
|
|
|
|
assert_equal 'missing digest for 1', e.message
|
|
end
|
|
|
|
def test_verify_no_trust
|
|
digests, signatures = dummy_signatures
|
|
|
|
use_ui @ui do
|
|
@low.verify [PUBLIC_CERT], nil, digests, signatures, 'some_gem'
|
|
end
|
|
|
|
assert_equal "WARNING: email:nobody@example is not trusted for some_gem\n",
|
|
@ui.error
|
|
|
|
assert_raise Gem::Security::Exception do
|
|
@medium.verify [PUBLIC_CERT], nil, digests, signatures
|
|
end
|
|
end
|
|
|
|
def test_verify_wrong_digest_type
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
data = OpenSSL::Digest.new('SHA512')
|
|
data << 'hello'
|
|
|
|
digests = { 'SHA512' => { 0 => data } }
|
|
signature = PRIVATE_KEY.sign 'sha512', data.digest
|
|
signatures = { 0 => signature }
|
|
|
|
e = assert_raise Gem::Security::Exception do
|
|
@almost_no.verify [PUBLIC_CERT], nil, digests, signatures
|
|
end
|
|
|
|
assert_equal 'no digests provided (probable bug)', e.message
|
|
end
|
|
|
|
def test_verify_signatures_chain
|
|
@spec.cert_chain = [PUBLIC_CERT, CHILD_CERT]
|
|
|
|
assert @chain.verify_signatures @spec, *dummy_signatures(CHILD_KEY)
|
|
end
|
|
|
|
def test_verify_signatures_data
|
|
@spec.cert_chain = [PUBLIC_CERT]
|
|
|
|
@almost_no.verify_signatures @spec, *dummy_signatures
|
|
end
|
|
|
|
def test_verify_signatures_root
|
|
@spec.cert_chain = [PUBLIC_CERT, CHILD_CERT]
|
|
|
|
assert @root.verify_signatures @spec, *dummy_signatures(CHILD_KEY)
|
|
end
|
|
|
|
def test_verify_signatures_signer
|
|
@spec.cert_chain = [PUBLIC_CERT]
|
|
|
|
assert @low.verify_signatures @spec, *dummy_signatures
|
|
end
|
|
|
|
def test_verify_signatures_trust
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
@spec.cert_chain = [PUBLIC_CERT]
|
|
|
|
assert @high.verify_signatures @spec, *dummy_signatures
|
|
end
|
|
|
|
def test_verify_signatures
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
@spec.cert_chain = [PUBLIC_CERT.to_s]
|
|
|
|
metadata_gz = Gem::Util.gzip @spec.to_yaml
|
|
|
|
package = Gem::Package.new 'nonexistent.gem'
|
|
package.checksums[Gem::Security::DIGEST_NAME] = {}
|
|
|
|
s = StringIO.new metadata_gz
|
|
def s.full_name() 'metadata.gz' end
|
|
|
|
digests = package.digest s
|
|
metadata_gz_digest = digests[Gem::Security::DIGEST_NAME]['metadata.gz']
|
|
|
|
signatures = {}
|
|
signatures['metadata.gz'] =
|
|
PRIVATE_KEY.sign @digest.new, metadata_gz_digest.digest
|
|
|
|
assert @high.verify_signatures @spec, digests, signatures
|
|
end
|
|
|
|
def test_verify_signatures_missing
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
@spec.cert_chain = [PUBLIC_CERT.to_s]
|
|
|
|
metadata_gz = Gem::Util.gzip @spec.to_yaml
|
|
|
|
package = Gem::Package.new 'nonexistent.gem'
|
|
package.checksums[Gem::Security::DIGEST_NAME] = {}
|
|
|
|
s = StringIO.new metadata_gz
|
|
def s.full_name() 'metadata.gz' end
|
|
|
|
digests = package.digest s
|
|
digests[Gem::Security::DIGEST_NAME]['data.tar.gz'] = @digest.hexdigest 'hello'
|
|
|
|
metadata_gz_digest = digests[Gem::Security::DIGEST_NAME]['metadata.gz']
|
|
|
|
signatures = {}
|
|
signatures['metadata.gz'] =
|
|
PRIVATE_KEY.sign @digest.new, metadata_gz_digest.digest
|
|
|
|
e = assert_raise Gem::Security::Exception do
|
|
@high.verify_signatures @spec, digests, signatures
|
|
end
|
|
|
|
assert_equal 'missing signature for data.tar.gz', e.message
|
|
end
|
|
|
|
def test_verify_signatures_none
|
|
Gem::Security.trust_dir.trust_cert PUBLIC_CERT
|
|
|
|
@spec.cert_chain = [PUBLIC_CERT.to_s]
|
|
|
|
metadata_gz = Gem::Util.gzip @spec.to_yaml
|
|
|
|
package = Gem::Package.new 'nonexistent.gem'
|
|
package.checksums[Gem::Security::DIGEST_NAME] = {}
|
|
|
|
s = StringIO.new metadata_gz
|
|
def s.full_name() 'metadata.gz' end
|
|
|
|
digests = package.digest s
|
|
digests[Gem::Security::DIGEST_NAME]['data.tar.gz'] = @digest.hexdigest 'hello'
|
|
|
|
assert_raise Gem::Security::Exception do
|
|
@high.verify_signatures @spec, digests, {}
|
|
end
|
|
end
|
|
|
|
def digest(data)
|
|
digester = @digest.new
|
|
digester << data
|
|
digester
|
|
end
|
|
|
|
def sign(data, key = PRIVATE_KEY)
|
|
key.sign @digest.new, data.digest
|
|
end
|
|
|
|
def dummy_signatures(key = PRIVATE_KEY)
|
|
data = digest 'hello'
|
|
|
|
digests = { Gem::Security::DIGEST_NAME => { 0 => data } }
|
|
signatures = { 0 => sign(data, key) }
|
|
|
|
return digests, signatures
|
|
end
|
|
end if Gem::HAVE_OPENSSL
|