1# coding: UTF-8 2 3require 'rubygems/test_case' 4 5class TestGemSecurityPolicy < Gem::TestCase 6 7 ALTERNATE_KEY = load_key 'alternate' 8 INVALID_KEY = load_key 'invalid' 9 CHILD_KEY = load_key 'child' 10 GRANDCHILD_KEY = load_key 'grandchild' 11 INVALIDCHILD_KEY = load_key 'invalidchild' 12 13 ALTERNATE_CERT = load_cert 'alternate' 14 CHILD_CERT = load_cert 'child' 15 EXPIRED_CERT = load_cert 'expired' 16 FUTURE_CERT = load_cert 'future' 17 GRANDCHILD_CERT = load_cert 'grandchild' 18 INVALIDCHILD_CERT = load_cert 'invalidchild' 19 INVALID_ISSUER_CERT = load_cert 'invalid_issuer' 20 INVALID_SIGNER_CERT = load_cert 'invalid_signer' 21 WRONG_KEY_CERT = load_cert 'wrong_key' 22 23 def setup 24 super 25 26 @spec = quick_gem 'a' do |s| 27 s.description = 'π' 28 s.files = %w[lib/code.rb] 29 end 30 31 @sha1 = OpenSSL::Digest::SHA1 32 @trust_dir = Gem::Security.trust_dir.dir # HACK use the object 33 34 @no = Gem::Security::NoSecurity 35 @almost_no = Gem::Security::AlmostNoSecurity 36 @low = Gem::Security::LowSecurity 37 @medium = Gem::Security::MediumSecurity 38 @high = Gem::Security::HighSecurity 39 40 @chain = Gem::Security::Policy.new( 41 'Chain', 42 :verify_data => true, 43 :verify_signer => true, 44 :verify_chain => true, 45 :verify_root => false, 46 :only_trusted => false, 47 :only_signed => false 48 ) 49 50 @root = Gem::Security::Policy.new( 51 'Root', 52 :verify_data => true, 53 :verify_signer => true, 54 :verify_chain => true, 55 :verify_root => true, 56 :only_trusted => false, 57 :only_signed => false 58 ) 59 end 60 61 def test_check_data 62 data = digest 'hello' 63 64 signature = sign data 65 66 assert @almost_no.check_data(PUBLIC_KEY, @sha1, signature, data) 67 end 68 69 def test_check_data_invalid 70 data = digest 'hello' 71 72 signature = sign data 73 74 invalid = digest 'hello!' 75 76 e = assert_raises Gem::Security::Exception do 77 @almost_no.check_data PUBLIC_KEY, @sha1, signature, invalid 78 end 79 80 assert_equal 'invalid signature', e.message 81 end 82 83 def test_check_chain 84 chain = [PUBLIC_CERT, CHILD_CERT, GRANDCHILD_CERT] 85 86 assert @chain.check_chain chain, Time.now 87 end 88 89 def test_check_chain_empty_chain 90 e = assert_raises Gem::Security::Exception do 91 @chain.check_chain [], Time.now 92 end 93 94 assert_equal 'empty signing chain', e.message 95 end 96 97 def test_check_chain_invalid 98 chain = [PUBLIC_CERT, CHILD_CERT, INVALIDCHILD_CERT] 99 100 e = assert_raises Gem::Security::Exception do 101 @chain.check_chain chain, Time.now 102 end 103 104 assert_equal "invalid signing chain: " + 105 "certificate #{INVALIDCHILD_CERT.subject} " + 106 "was not issued by #{CHILD_CERT.subject}", e.message 107 end 108 109 def test_check_chain_no_chain 110 e = assert_raises Gem::Security::Exception do 111 @chain.check_chain nil, Time.now 112 end 113 114 assert_equal 'missing signing chain', e.message 115 end 116 117 def test_check_cert 118 assert @low.check_cert(PUBLIC_CERT, nil, Time.now) 119 end 120 121 def test_check_cert_expired 122 e = assert_raises Gem::Security::Exception do 123 @low.check_cert EXPIRED_CERT, nil, Time.now 124 end 125 126 assert_equal "certificate #{EXPIRED_CERT.subject} " + 127 "not valid after #{EXPIRED_CERT.not_after}", 128 e.message 129 end 130 131 def test_check_cert_future 132 e = assert_raises Gem::Security::Exception do 133 @low.check_cert FUTURE_CERT, nil, Time.now 134 end 135 136 assert_equal "certificate #{FUTURE_CERT.subject} " + 137 "not valid before #{FUTURE_CERT.not_before}", 138 e.message 139 end 140 141 def test_check_cert_invalid_issuer 142 e = assert_raises Gem::Security::Exception do 143 @low.check_cert INVALID_ISSUER_CERT, PUBLIC_CERT, Time.now 144 end 145 146 assert_equal "certificate #{INVALID_ISSUER_CERT.subject} " + 147 "was not issued by #{PUBLIC_CERT.subject}", 148 e.message 149 end 150 151 def test_check_cert_issuer 152 assert @low.check_cert(CHILD_CERT, PUBLIC_CERT, Time.now) 153 end 154 155 def test_check_cert_no_signer 156 e = assert_raises Gem::Security::Exception do 157 @high.check_cert(nil, nil, Time.now) 158 end 159 160 assert_equal 'missing signing certificate', e.message 161 end 162 163 def test_check_key 164 assert @almost_no.check_key(PUBLIC_CERT, PRIVATE_KEY) 165 end 166 167 def test_check_key_no_signer 168 assert @almost_no.check_key(nil, nil) 169 170 e = assert_raises Gem::Security::Exception do 171 @high.check_key(nil, nil) 172 end 173 174 assert_equal 'missing key or signature', e.message 175 end 176 177 def test_check_key_wrong_key 178 e = assert_raises Gem::Security::Exception do 179 @almost_no.check_key(PUBLIC_CERT, ALTERNATE_KEY) 180 end 181 182 assert_equal "certificate #{PUBLIC_CERT.subject} " + 183 "does not match the signing key", e.message 184 end 185 186 def test_check_root 187 chain = [PUBLIC_CERT, CHILD_CERT, INVALIDCHILD_CERT] 188 189 assert @chain.check_root chain, Time.now 190 end 191 192 def test_check_root_empty_chain 193 e = assert_raises Gem::Security::Exception do 194 @chain.check_root [], Time.now 195 end 196 197 assert_equal 'missing root certificate', e.message 198 end 199 200 def test_check_root_invalid_signer 201 chain = [INVALID_SIGNER_CERT] 202 203 e = assert_raises Gem::Security::Exception do 204 @chain.check_root chain, Time.now 205 end 206 207 assert_equal "certificate #{INVALID_SIGNER_CERT.subject} " + 208 "was not issued by #{INVALID_SIGNER_CERT.issuer}", 209 e.message 210 end 211 212 def test_check_root_not_self_signed 213 chain = [INVALID_ISSUER_CERT] 214 215 e = assert_raises Gem::Security::Exception do 216 @chain.check_root chain, Time.now 217 end 218 219 assert_equal "root certificate #{INVALID_ISSUER_CERT.subject} " + 220 "is not self-signed (issuer #{INVALID_ISSUER_CERT.issuer})", 221 e.message 222 end 223 224 def test_check_root_no_chain 225 e = assert_raises Gem::Security::Exception do 226 @chain.check_root nil, Time.now 227 end 228 229 assert_equal 'missing signing chain', e.message 230 end 231 232 def test_check_trust 233 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 234 235 assert @high.check_trust [PUBLIC_CERT], @sha1, @trust_dir 236 end 237 238 def test_check_trust_child 239 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 240 241 assert @high.check_trust [PUBLIC_CERT, CHILD_CERT], @sha1, @trust_dir 242 end 243 244 def test_check_trust_empty_chain 245 e = assert_raises Gem::Security::Exception do 246 @chain.check_trust [], @sha1, @trust_dir 247 end 248 249 assert_equal 'missing root certificate', e.message 250 end 251 252 def test_check_trust_mismatch 253 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 254 255 e = assert_raises Gem::Security::Exception do 256 @high.check_trust [WRONG_KEY_CERT], @sha1, @trust_dir 257 end 258 259 assert_equal "trusted root certificate #{PUBLIC_CERT.subject} checksum " + 260 "does not match signing root certificate checksum", e.message 261 end 262 263 def test_check_trust_no_chain 264 e = assert_raises Gem::Security::Exception do 265 @chain.check_trust nil, @sha1, @trust_dir 266 end 267 268 assert_equal 'missing signing chain', e.message 269 end 270 271 def test_check_trust_no_trust 272 e = assert_raises Gem::Security::Exception do 273 @high.check_trust [PUBLIC_CERT], @sha1, @trust_dir 274 end 275 276 assert_equal "root cert #{PUBLIC_CERT.subject} is not trusted", e.message 277 end 278 279 def test_check_trust_no_trust_child 280 e = assert_raises Gem::Security::Exception do 281 @high.check_trust [PUBLIC_CERT, CHILD_CERT], @sha1, @trust_dir 282 end 283 284 assert_equal "root cert #{PUBLIC_CERT.subject} is not trusted " + 285 "(root of signing cert #{CHILD_CERT.subject})", e.message 286 end 287 288 def test_verify 289 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 290 291 assert @almost_no.verify [PUBLIC_CERT], nil, *dummy_signatures 292 end 293 294 def test_verify_chain_signatures 295 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 296 297 assert @high.verify [PUBLIC_CERT], nil, *dummy_signatures 298 end 299 300 def test_verify_chain_key 301 @almost_no.verify [PUBLIC_CERT], PRIVATE_KEY, *dummy_signatures 302 end 303 304 def test_verify_no_digests 305 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 306 307 _, signatures = dummy_signatures 308 309 e = assert_raises Gem::Security::Exception do 310 @almost_no.verify [PUBLIC_CERT], nil, {}, signatures 311 end 312 313 assert_equal 'no digests provided (probable bug)', e.message 314 end 315 316 def test_verify_no_digests_no_security 317 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 318 319 _, signatures = dummy_signatures 320 321 e = assert_raises Gem::Security::Exception do 322 @no.verify [PUBLIC_CERT], nil, {}, signatures 323 end 324 325 assert_equal 'missing digest for 0', e.message 326 end 327 328 def test_verify_not_enough_signatures 329 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 330 331 digests, signatures = dummy_signatures 332 333 data = digest 'goodbye' 334 335 signatures[1] = PRIVATE_KEY.sign @sha1.new, data.digest 336 337 e = assert_raises Gem::Security::Exception do 338 @almost_no.verify [PUBLIC_CERT], nil, digests, signatures 339 end 340 341 assert_equal 'missing digest for 1', e.message 342 end 343 344 def test_verify_wrong_digest_type 345 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 346 347 sha512 = OpenSSL::Digest::SHA512 348 349 data = sha512.new 350 data << 'hello' 351 352 digests = { 'SHA512' => { 0 => data } } 353 signature = PRIVATE_KEY.sign sha512.new, data.digest 354 signatures = { 0 => signature } 355 356 e = assert_raises Gem::Security::Exception do 357 @almost_no.verify [PUBLIC_CERT], nil, digests, signatures 358 end 359 360 assert_equal 'no digests provided (probable bug)', e.message 361 end 362 363 def test_verify_signatures_chain 364 @spec.cert_chain = [PUBLIC_CERT, CHILD_CERT] 365 366 assert @chain.verify_signatures @spec, *dummy_signatures(CHILD_KEY) 367 end 368 369 def test_verify_signatures_data 370 @spec.cert_chain = [PUBLIC_CERT] 371 372 @almost_no.verify_signatures @spec, *dummy_signatures 373 end 374 375 def test_verify_signatures_root 376 @spec.cert_chain = [PUBLIC_CERT, CHILD_CERT] 377 378 assert @root.verify_signatures @spec, *dummy_signatures(CHILD_KEY) 379 end 380 381 def test_verify_signatures_signer 382 @spec.cert_chain = [PUBLIC_CERT] 383 384 assert @low.verify_signatures @spec, *dummy_signatures 385 end 386 387 def test_verify_signatures_trust 388 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 389 390 @spec.cert_chain = [PUBLIC_CERT] 391 392 assert @high.verify_signatures @spec, *dummy_signatures 393 end 394 395 def test_verify_signatures 396 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 397 398 @spec.cert_chain = [PUBLIC_CERT.to_s] 399 400 metadata_gz = Gem.gzip @spec.to_yaml 401 402 package = Gem::Package.new 'nonexistent.gem' 403 package.checksums['SHA1'] = {} 404 405 s = StringIO.new metadata_gz 406 def s.full_name() 'metadata.gz' end 407 408 digests = package.digest s 409 metadata_gz_digest = digests['SHA1']['metadata.gz'] 410 411 signatures = {} 412 signatures['metadata.gz'] = 413 PRIVATE_KEY.sign @sha1.new, metadata_gz_digest.digest 414 415 assert @high.verify_signatures @spec, digests, signatures 416 end 417 418 def test_verify_signatures_missing 419 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 420 421 @spec.cert_chain = [PUBLIC_CERT.to_s] 422 423 metadata_gz = Gem.gzip @spec.to_yaml 424 425 package = Gem::Package.new 'nonexistent.gem' 426 package.checksums['SHA1'] = {} 427 428 s = StringIO.new metadata_gz 429 def s.full_name() 'metadata.gz' end 430 431 digests = package.digest s 432 digests['SHA1']['data.tar.gz'] = OpenSSL::Digest.new 'SHA1', 'hello' 433 434 metadata_gz_digest = digests['SHA1']['metadata.gz'] 435 436 signatures = {} 437 signatures['metadata.gz'] = 438 PRIVATE_KEY.sign @sha1.new, metadata_gz_digest.digest 439 440 e = assert_raises Gem::Security::Exception do 441 @high.verify_signatures @spec, digests, signatures 442 end 443 444 assert_equal 'missing signature for data.tar.gz', e.message 445 end 446 447 def test_verify_signatures_none 448 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 449 450 @spec.cert_chain = [PUBLIC_CERT.to_s] 451 452 metadata_gz = Gem.gzip @spec.to_yaml 453 454 package = Gem::Package.new 'nonexistent.gem' 455 package.checksums['SHA1'] = {} 456 457 s = StringIO.new metadata_gz 458 def s.full_name() 'metadata.gz' end 459 460 digests = package.digest s 461 digests['SHA1']['data.tar.gz'] = OpenSSL::Digest.new 'SHA1', 'hello' 462 463 assert_raises Gem::Security::Exception do 464 @almost_no.verify_signatures @spec, digests, {} 465 end 466 end 467 468 def digest data 469 digester = @sha1.new 470 digester << data 471 digester 472 end 473 474 def sign data, key = PRIVATE_KEY 475 key.sign @sha1.new, data.digest 476 end 477 478 def dummy_signatures key = PRIVATE_KEY 479 data = digest 'hello' 480 481 digests = { 'SHA1' => { 0 => data } } 482 signatures = { 0 => sign(data, key) } 483 484 return digests, signatures 485 end 486 487end 488 489