├── .document ├── .gitignore ├── .rspec ├── .travis.yml ├── .yardopts ├── ChangeLog.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib ├── sslyze.rb └── sslyze │ ├── cipher_suites.rb │ ├── program.rb │ ├── task.rb │ ├── version.rb │ ├── x509 │ ├── domain.rb │ ├── extension.rb │ ├── extension_set.rb │ ├── extensions.rb │ ├── extensions │ │ ├── basic_constraints.rb │ │ ├── certificate_policies.rb │ │ ├── crl_distribution_points.rb │ │ ├── extended_key_usage.rb │ │ ├── key_usage.rb │ │ └── subject_alt_name.rb │ └── name.rb │ ├── xml.rb │ └── xml │ ├── attributes.rb │ ├── attributes │ ├── error.rb │ ├── exception.rb │ ├── is_supported.rb │ ├── is_vulnerable.rb │ └── title.rb │ ├── certinfo.rb │ ├── certinfo │ ├── certificate.rb │ ├── certificate │ │ └── public_key.rb │ ├── certificate_validation.rb │ ├── certificate_validation │ │ ├── hostname_validation.rb │ │ ├── path_validation.rb │ │ └── verified_certificate_chain.rb │ ├── has_certificates.rb │ ├── ocsp_stapling.rb │ ├── ocsp_stapling │ │ └── ocsp_response.rb │ └── received_certificate_chain.rb │ ├── compression.rb │ ├── compression │ └── compression_method.rb │ ├── fallback.rb │ ├── fallback │ └── tls_fallback_scsv.rb │ ├── heartbleed.rb │ ├── heartbleed │ └── openssl_heartbleed.rb │ ├── http_headers.rb │ ├── http_headers │ ├── http_public_key_pinning.rb │ └── http_strict_transport_security.rb │ ├── invalid_target.rb │ ├── openssl_ccs.rb │ ├── openssl_ccs │ └── openssl_ccs_injection.rb │ ├── plugin.rb │ ├── protocol.rb │ ├── protocol │ ├── cipher_suite.rb │ └── cipher_suite │ │ └── key_exchange.rb │ ├── reneg.rb │ ├── reneg │ └── session_renegotiation.rb │ ├── resum.rb │ ├── resum │ ├── session_resumption_with_session_ids.rb │ └── session_resumption_with_tls_tickets.rb │ ├── resum_rate.rb │ ├── target.rb │ └── types.rb ├── ruby-sslyze.gemspec └── spec ├── spec_helper.rb ├── sslyze.xml ├── sslyze_spec.rb ├── x509 ├── domain_spec.rb ├── extension_set_spec.rb ├── extension_spec.rb ├── extensions │ ├── basic_constraints_spec.rb │ ├── certificate_policies_spec.rb │ ├── crl_distribution_points_spec.rb │ ├── extended_key_usage_spec.rb │ ├── key_usage_spec.rb │ └── subject_alt_name_spec.rb └── name_spec.rb ├── xml ├── certinfo │ ├── certificate │ │ └── public_key_spec.rb │ ├── certificate_spec.rb │ ├── certificate_validation │ │ ├── hostname_validation_spec.rb │ │ ├── path_validation_spec.rb │ │ └── verified_certificate_chain_spec.rb │ ├── certificate_validation_spec.rb │ ├── ocsp_stapling │ │ └── ocsp_response_spec.rb │ ├── ocsp_stapling_spec.rb │ └── received_certificate_chain_spec.rb ├── certinfo_spec.rb ├── compression │ └── compression_method_spec.rb ├── compression_spec.rb ├── heartbleed │ └── openssl_heartbleed_spec.rb ├── heartbleed_spec.rb ├── http_headers │ ├── http_public_key_pinning_spec.rb │ └── http_strict_transport_security_spec.rb ├── http_headers_spec.rb ├── invalid_target_spec.rb ├── plugin_examples.rb ├── protocol │ ├── cipher_suite │ │ └── key_exchange_spec.rb │ └── cipher_suite_spec.rb ├── protocol_spec.rb ├── reneg │ └── session_renegotiation_spec.rb ├── reneg_spec.rb ├── resum │ ├── session_resumption_with_session_ids_spec.rb │ └── session_resumption_with_tls_tickets_spec.rb ├── resum_rate_spec.rb ├── resum_spec.rb └── target_spec.rb ├── xml_examples.rb └── xml_spec.rb /.document: -------------------------------------------------------------------------------- 1 | - 2 | ChangeLog.md 3 | LICENSE.txt 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Gemfile.lock 2 | /coverage 3 | /doc/ 4 | /pkg/ 5 | /vendor/cache/*.gem 6 | /env 7 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour --format documentation 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - CC_TEST_REPORTER_ID=2a03fa37ce5a5cb21bb117a736be5d83dcf9f1c3ea2b248f7af4c0a7b330d8c8 4 | 5 | language: ruby 6 | before_install: 7 | - gem update --system 8 | - gem install bundler -v "~> 2.0" 9 | - pip install --upgrade --user pip setuptools 10 | - pip install --upgrade --user nassl sslyze 11 | 12 | rvm: 13 | - 2.5 14 | - 2.6 15 | - 2.7 16 | - jruby 17 | matrix: 18 | allow_failures: 19 | - rvm: jruby 20 | 21 | before_script: 22 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 23 | - chmod +x ./cc-test-reporter 24 | - ./cc-test-reporter before-build 25 | 26 | script: 27 | - bundle exec rake spec 28 | 29 | after_script: 30 | - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT 31 | 32 | notifications: 33 | slack: 34 | secure: IfKhtia5nM6KA9nK8jiSkNnVOLN96er6gK5jgjYKFNrVyWAKRUJZ0TB9L+igjUWDq7t+tRvj8yGT2k61xVJgF+ZDlQiWvyazTsgQeqbjieCxCrj/BTGZLyD1hhOLg7vqpyeQvp/34hDahx6XNp6XPvkxeofjc0H6STv2UjJkpQk= 35 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown --title "ruby-sslyze Documentation" --protected 2 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | ### 1.2.1 / 2020-05-05 2 | 3 | * Allow {SSLyze::XML#total_scan_time} to return `nil`, if the `totalScanTime` 4 | attribute is missing. 5 | 6 | ### 1.2.0 / 2018-04-03 7 | 8 | * Replaced `SSLyze::X509::PublicKey` with 9 | {SSLyze::XML::Certinfo::Certificate::PublicKey}. 10 | 11 | ### 1.1.0 / 2018-03-12 12 | 13 | * Require [sslyze] >= 1.4.0 14 | * Added {SSLyze::XML::InvalidTarget#target}. 15 | * Added {SSLyze::XML::InvalidTarget#port}. 16 | * Added the `--update_trust_stores` option. 17 | * Added the `--robot` option. 18 | * Replaced the `--timeout` and `--nb_retries` options with `--slow_connection`. 19 | 20 | ### 1.0.0 / 2018-03-06 21 | 22 | * Require [sslyze] >= 1.3.4. 23 | * Added {SSLyze::X509::Domain}. 24 | * Added {SSLyze::X509::Extension}. 25 | * Added {SSLyze::X509::ExtensionSet}. 26 | * Added {SSLyze::X509::Extensions::BasicConstraints}. 27 | * Added {SSLyze::X509::Extensions::CertificatePolicies}. 28 | * Added {SSLyze::X509::Extensions::CRLDistributionPoints}. 29 | * Added {SSLyze::X509::Extensions::ExtendedKeyUsage}. 30 | * Added {SSLyze::X509::Extensions::KeyUsage}. 31 | * Added {SSLyze::X509::Extensions::SubjectAltName}. 32 | * Added {SSLyze::X509::Name}. 33 | * Added `SSLyze::X509::PublicKey`. 34 | * Moved all XML related classes into {SSLyze::XML}. 35 | * Updated {SSLyze::XML} and classes to represent the current sslyze 1.3.4 XSD. 36 | 37 | ### 0.2.1 / 2017-01-13 38 | 39 | * Fix file descriptor leak in {SSLyze::XML.open} by using 40 | `File.open(path) { |file| ... }` instead of `File.new(path)`, which keeps the 41 | file descriptor open until GC collects the `File` instance. 42 | 43 | ### 0.2.0 / 2016-08-16 44 | 45 | * Requires sslyze 0.12.x. 46 | * Added `SSLyze::XML#each_invalid_target`. 47 | * Added `SSLyze::XML#invalid_targets`. 48 | * Added `SSLyze::InvalidTarget`. 49 | * Added `SSLyze::Target#ssl_v2` alias. 50 | * Added `SSLyze::Target#ssl_v3` alias. 51 | * Added `SSLyze::Target#tls_v1` alias. 52 | * Added `SSLyze::Target#tls_v1_1` alias. 53 | * Added `SSLyze::Target#tls_v1_2` alias. 54 | * Added `SSLyze::CertificateValidation#path?`. 55 | * Added `SSLyze::CertificateValidation#results`. 56 | * Fixed a bug in `SSLyze::CertInfo#validation` when the `certificateValidation` 57 | node is omitted. 58 | 59 | ### 0.1.1 / 2015-12-08 60 | 61 | * `certificateValidation` may be omitted from `certinfo` if an OpenSSL 62 | exception occurred. Allow `SSLyze::CertInfo#validation` may return `nil`. 63 | 64 | ### 0.1.0 / 2015-10-13 65 | 66 | * Initial release: 67 | * Provides a Ruby interface to `sslyze.py`. 68 | * Provides a Parser for consuming the sslyze XML output. 69 | * [sslyze] >= 0.12 70 | 71 | [sslyze]: https://github.com/nabla-c0d3/sslyze#readme 72 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org/' 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem 'rake' 7 | gem 'rubygems-tasks', '~> 0.2' 8 | 9 | gem 'rspec', '~> 3.0' 10 | 11 | gem 'yard', '~> 0.9' 12 | gem 'kramdown' 13 | end 14 | 15 | group :test do 16 | gem 'json' 17 | gem 'simplecov', require: nil 18 | end 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2020 Hal Brodigan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ruby-sslyze 2 | 3 | [![Code Climate](https://codeclimate.com/github/trailofbits/ruby-sslyze/badges/gpa.svg)](https://codeclimate.com/github/trailofbits/ruby-sslyze) 4 | [![Test Coverage](https://codeclimate.com/github/trailofbits/ruby-sslyze/badges/coverage.svg)](https://codeclimate.com/github/trailofbits/ruby-sslyze) 5 | [![Build Status](https://travis-ci.org/trailofbits/ruby-sslyze.svg)](https://travis-ci.org/trailofbits/ruby-sslyze) 6 | 7 | * [Homepage](https://github.com/trailofbits/ruby-sslyze#readme) 8 | * [Issues](https://github.com/trailofbits/ruby-sslyze/issues) 9 | * [Documentation](http://rubydoc.info/gems/ruby-sslyze/frames) 10 | 11 | ## Description 12 | 13 | A Ruby interface to [sslyze] python utility. 14 | 15 | ## Features 16 | 17 | * Provides a Ruby interface to `sslyze.py`. 18 | * Provides a Parser for consuming the sslyze XML output. 19 | * Supports [sslyze] >= 1.4.0 20 | 21 | ## Examples 22 | 23 | Analyze a domain: 24 | 25 | require 'sslyze' 26 | 27 | SSLyze::Program.analyze(targets: 'twitter.com', regular: true) 28 | 29 | Analyze multiple domains: 30 | 31 | SSLyze::Program.analyze( 32 | targets: ['twitter.com', 'github.com'], 33 | regular: true 34 | ) 35 | 36 | Output to XML: 37 | 38 | SSLyze::Program.analyze( 39 | targets: 'twitter.com', 40 | regular: true, 41 | xml_out: 'path/to/xml' 42 | ) 43 | 44 | Parsing sslyze XML output: 45 | 46 | xml = SSLyze::XML.open('path/to/xml') 47 | 48 | ## Requirements 49 | 50 | * [rprogram] ~> 0.3 51 | * [nokogiri] ~> 1.8 52 | * [sslyze] >= 1.4.0 53 | 54 | ## Install 55 | 56 | $ pip install sslyze 57 | $ gem install ruby-sslyze 58 | 59 | ## Copyright 60 | 61 | Copyright (c) 2014-2020 Hal Brodigan 62 | 63 | See {file:LICENSE.txt} for details. 64 | 65 | [sslyze]: https://github.com/nabla-c0d3/sslyze#readme 66 | 67 | [rpgoram]: https://github.com/postmodern/rprogram#readme 68 | [nokogiri]: http://www.nokogiri.org/ 69 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'bundler/setup' 3 | rescue LoadError => e 4 | abort e.message 5 | end 6 | 7 | require 'rake' 8 | require 'rubygems/tasks' 9 | Gem::Tasks.new 10 | 11 | require 'rspec/core/rake_task' 12 | RSpec::Core::RakeTask.new 13 | 14 | task :test => :spec 15 | task :default => :spec 16 | 17 | require 'yard' 18 | YARD::Rake::YardocTask.new 19 | task :doc => :yard 20 | 21 | file 'spec/sslyze.xml' do 22 | sh 'sslyze --xml_out spec/sslyze.xml --regular --resum_rate --http_headers twitter.com github.com:443 www.yahoo.com:443 google.com:443 foo bar' 23 | end 24 | -------------------------------------------------------------------------------- /lib/sslyze.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/program' 2 | require 'sslyze/xml' 3 | require 'sslyze/version' 4 | -------------------------------------------------------------------------------- /lib/sslyze/program.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/task' 2 | 3 | require 'rprogram/program' 4 | 5 | module SSLyze 6 | # 7 | # Represents the `sslyze` command line utility. 8 | # 9 | class Program < RProgram::Program 10 | 11 | name_program 'sslyze' 12 | 13 | # 14 | # Finds the `sslyze` script and runs it. 15 | # 16 | # @param [Hash{Symbol => Object}] options 17 | # Additional options for `sslyze`. 18 | # 19 | # @param [Hash{Symbol => Object}] exec_options 20 | # Additional exec-options. 21 | # 22 | # @yield [task] 23 | # If a block is given, it will be passed a task object 24 | # used to specify options for `sslyze`. 25 | # 26 | # @yieldparam [Task] task 27 | # The sslyze task object. 28 | # 29 | # @return [Boolean] 30 | # Specifies whether the command exited normally. 31 | # 32 | # @see http://rubydoc.info/gems/rprogram/0.3.0/RProgram/Program#run-instance_method 33 | # For additional exec-options. 34 | # 35 | def self.analyze(options={},exec_options={},&block) 36 | find.analyze(options,exec_options,&block) 37 | end 38 | 39 | # 40 | # Runs `sslyze`. 41 | # 42 | # @param [Hash{Symbol => Object}] options 43 | # Additional options for `sslyze`. 44 | # 45 | # @param [Hash{Symbol => Object}] exec_options 46 | # Additional exec-options. 47 | # 48 | # @yield [task] 49 | # If a block is given, it will be passed a task object 50 | # used to specify options for `sslyze`. 51 | # 52 | # @yieldparam [Task] task 53 | # The sslyze task object. 54 | # 55 | # @return [Boolean] 56 | # Specifies whether the command exited normally. 57 | # 58 | # @see http://rubydoc.info/gems/rprogram/0.3.0/RProgram/Program#run-instance_method 59 | # For additional exec-options. 60 | # 61 | def analyze(options={},exec_options={},&block) 62 | run_task(Task.new(options,&block),exec_options) 63 | end 64 | 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/sslyze/task.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/task' 2 | 3 | module SSLyze 4 | # 5 | # Represents options for {Program}. 6 | # 7 | class Task < RProgram::Task 8 | 9 | # Options: 10 | long_option flag: '--version' 11 | long_option flag: '--help' 12 | long_option flag: '--regular' 13 | 14 | # Trust stores options: 15 | long_option flag: '--update_trust_stores' 16 | 17 | # Client certificate support: 18 | long_option flag: '--cert', equals: true 19 | long_option flag: '--key', equals: true 20 | long_option flag: '--keyform', equals: true 21 | long_option flag: '--pass', equals: true 22 | 23 | # Input and output options: 24 | long_option flag: '--xml_out', equals: true 25 | long_option flag: '--json_out', equals: true 26 | long_option flag: '--targets_in', equals: true 27 | long_option flag: '--quiet' 28 | 29 | # Connectivity options: 30 | long_option flag: '--slow_connection' 31 | long_option flag: '--https_tunnel', equals: true 32 | long_option flag: '--starttls', equals: true 33 | long_option flag: '--xmpp_to', equals: true 34 | long_option flag: '--sni', equals: true 35 | 36 | # SessionResumptionPlugin: 37 | long_option flag: '--resum' 38 | long_option flag: '--resum_rate' 39 | 40 | # HeartbleedPlugin: 41 | long_option flag: '--heartbleed' 42 | 43 | # CertificateInfoPlugin: 44 | long_option flag: '--certinfo' 45 | long_option flag: '--ca_file', equals: true 46 | 47 | # SessionRenegotiationPlugin: 48 | long_option flag: '--reneg' 49 | 50 | # OpenSslCipherSuitesPlugin: 51 | long_option flag: '--sslv2' 52 | long_option flag: '--sslv3' 53 | long_option flag: '--tlsv1' 54 | long_option flag: '--tlsv1_1' 55 | long_option flag: '--tlsv1_2' 56 | long_option flag: '--http_get' 57 | long_option flag: '--hide_rejected_ciphers' 58 | 59 | # HttpHeadersPlugin: 60 | long_option flag: '--http_headers' 61 | 62 | # CompressionPlugin: 63 | long_option flag: '--compression' 64 | 65 | # RobotPlugin: 66 | long_option flag: '--robot' 67 | 68 | # FallbackScsvPlugin: 69 | long_option flag: '--fallback' 70 | 71 | # OpenSslCcsInjectionPlugin: 72 | long_option flag: '--openssl_ccs' 73 | 74 | non_option name: :targets, tailing: true 75 | 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/sslyze/version.rb: -------------------------------------------------------------------------------- 1 | module SSLyze 2 | # ruby-sslyze version 3 | VERSION = '1.2.1' 4 | end 5 | -------------------------------------------------------------------------------- /lib/sslyze/x509/domain.rb: -------------------------------------------------------------------------------- 1 | module SSLyze 2 | module X509 3 | # 4 | # Represents a domain name pattern. 5 | # 6 | # @since 1.0.0 7 | # 8 | class Domain 9 | 10 | # The subject name. 11 | # 12 | # @return [String] 13 | attr_reader :name 14 | 15 | # The domain part of the subject name. 16 | # 17 | # @return [String] 18 | attr_reader :domain 19 | 20 | # The literal suffix of the subject name. 21 | # 22 | # @return [String] 23 | attr_reader :suffix 24 | 25 | # 26 | # Initializes the subject name. 27 | # 28 | # @param [String] name 29 | # The subject name. 30 | # 31 | def initialize(name) 32 | @name = name 33 | 34 | if @name.start_with?('*.') 35 | @suffix = @name[1..-1] 36 | @domain = @name[2..-1] 37 | else 38 | @domain = @name 39 | end 40 | end 41 | 42 | # 43 | # Compares two subject names. 44 | # 45 | # @return [Boolean] 46 | # 47 | def ==(other) 48 | other.kind_of?(self.class) && @name == other.name 49 | end 50 | 51 | # 52 | # Tests whether the domain is matched by the subject name. 53 | # 54 | def include?(domain) 55 | if @name.start_with?('*.') # wildcard 56 | domain.end_with?(@suffix) || # does the domain share the suffix 57 | domain == @domain # does the domain match the suffix 58 | else # exact match 59 | domain == @name 60 | end 61 | end 62 | 63 | alias === include? 64 | 65 | alias to_s name 66 | alias to_str name 67 | 68 | # 69 | # Inspects the subject name. 70 | # 71 | # @return [String] 72 | # 73 | def inspect 74 | "#<#{self.class}: #{self}>" 75 | end 76 | 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/sslyze/x509/extension.rb: -------------------------------------------------------------------------------- 1 | require 'delegate' 2 | 3 | module SSLyze 4 | module X509 5 | # 6 | # Wraps around an [OpenSSL::X509::Extension][1] object. 7 | # 8 | # @see http://www.rubydoc.info/stdlib/openssl/OpenSSL/X509/Extension 9 | # 10 | # @since 1.0.0 11 | # 12 | class Extension < SimpleDelegator 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/sslyze/x509/extension_set.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/x509/extensions' 2 | 3 | module SSLyze 4 | module X509 5 | # 6 | # Provides a Hash-like interface around an Array of 7 | # [OpenSSL::X5095::Extension][1]s. 8 | # 9 | # [1]: http://www.rubydoc.info/stdlib/openssl/OpenSSL/X509/Extension 10 | # 11 | # @since 1.0.0 12 | # 13 | class ExtensionSet 14 | 15 | include Enumerable 16 | 17 | # 18 | # Initializes the X509 extension set. 19 | # 20 | # @param [Array] extensions 21 | # The array of extensions. 22 | # 23 | def initialize(extensions) 24 | @extensions = Hash[extensions.map { |ext| 25 | [ext.oid, ext] 26 | }] 27 | end 28 | 29 | # 30 | # Enumerates over the X509 extensions in the set. 31 | # 32 | # @yield [extension] 33 | # 34 | # @yieldparam [OpenSSL::X509::Extension] extension 35 | # 36 | # @return [Enumerator] 37 | # 38 | def each(&block) 39 | @extensions.each_value(&block) 40 | end 41 | 42 | # 43 | # Determines if the X509 extension exists in the set. 44 | # 45 | # @param [String] oid 46 | # 47 | # @return [Boolean] 48 | # 49 | def has?(oid) 50 | @extensions.has_key?(oid) 51 | end 52 | 53 | # 54 | # Looks up the X509 extension with the given name. 55 | # @param [String] oid 56 | # 57 | # @return [OpenSSL::X509::Extension] 58 | # 59 | def [](oid) 60 | @extensions[oid] 61 | end 62 | 63 | # 64 | # Converts the X509 extension set to an Array. 65 | # 66 | # @return [Array] 67 | # 68 | def to_a 69 | @extensions.values 70 | end 71 | 72 | # 73 | # The `basicConstraints` extension. 74 | # 75 | # @return [Extensions::BasicConstraints, nil] 76 | # 77 | def basic_constraints 78 | @basic_constraints ||= if (ext = self['basicConstraints']) 79 | Extensions::BasicConstraints.new(ext) 80 | end 81 | end 82 | 83 | # 84 | # The `certificatePolicies` extension. 85 | # 86 | # @return [Extensions::CertificatePolicies, nil] 87 | # 88 | def certificate_policies 89 | @certificate_policies ||= if (ext = self['certificatePolicies']) 90 | Extensions::CertificatePolicies.new(ext) 91 | end 92 | end 93 | 94 | # 95 | # The `crlDistributionPoints` extension. 96 | # 97 | # @return [Extensions::CRLDistributionPoints, nil] 98 | # 99 | def crl_distribution_points 100 | @crl_distribution_points ||= if (ext = self['crlDistributionPoints']) 101 | Extensions::CRLDistributionPoints.new(ext) 102 | end 103 | end 104 | 105 | # 106 | # The `extendedKeyUsage` extension. 107 | # 108 | # @return [Extensions::ExtendedKeyUsage, nil] 109 | # 110 | def extended_key_usage 111 | @extended_key_usage ||= if (ext = self['extendedKeyUsage']) 112 | Extensions::ExtendedKeyUsage.new(ext) 113 | end 114 | end 115 | 116 | # 117 | # The `keyUsage` extension. 118 | # 119 | # @return [Extensions::KeyUsage, nil] 120 | # 121 | def key_usage 122 | @key_usage ||= if (ext = self['keyUsage']) 123 | Extensions::KeyUsage.new(ext) 124 | end 125 | end 126 | 127 | # 128 | # The `subjectAltName` extension. 129 | # 130 | # @return [Extensions::SubjectAltName, nil] 131 | # 132 | def subject_alt_name 133 | @subject_alt_name ||= if (ext = self['subjectAltName']) 134 | Extensions::SubjectAltName.new(ext) 135 | end 136 | end 137 | 138 | end 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /lib/sslyze/x509/extensions.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/x509/extensions/basic_constraints' 2 | require 'sslyze/x509/extensions/certificate_policies' 3 | require 'sslyze/x509/extensions/crl_distribution_points' 4 | require 'sslyze/x509/extensions/extended_key_usage' 5 | require 'sslyze/x509/extensions/key_usage' 6 | require 'sslyze/x509/extensions/subject_alt_name' 7 | -------------------------------------------------------------------------------- /lib/sslyze/x509/extensions/basic_constraints.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/x509/extension' 2 | 3 | module SSLyze 4 | module X509 5 | module Extensions 6 | # 7 | # Represents the `basicConstraints` X509v3 extension. 8 | # 9 | # @since 1.0.0 10 | # 11 | class BasicConstraints < Extension 12 | 13 | # 14 | # The value of the `CA` constraint. 15 | # 16 | # @return [Boolean, nil] 17 | # 18 | def ca? 19 | if value.include?('CA:TRUE') then true 20 | elsif value.include?('CA:FALSE') then false 21 | end 22 | end 23 | 24 | # 25 | # The value of the `pathlen` constraint. 26 | # 27 | # @return [Integer, nil] 28 | # 29 | def path_length 30 | @path_length ||= if (match = value.match(/pathlen:(\d+)/)) 31 | match[1].to_i 32 | end 33 | end 34 | 35 | alias path_len path_length 36 | alias pathlen path_length 37 | 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/sslyze/x509/extensions/certificate_policies.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/x509/extension' 2 | 3 | require 'uri' 4 | 5 | module SSLyze 6 | module X509 7 | module Extensions 8 | # 9 | # Represents the `certificatePolicies` X509v3 extension. 10 | # 11 | # @since 1.0.0 12 | # 13 | class CertificatePolicies < Extension 14 | 15 | # 16 | # Represents an individual certificate policy. 17 | # 18 | class Policy 19 | 20 | # @return [String] 21 | attr_reader :policy 22 | 23 | # @return [URI::Generic, nil] 24 | attr_reader :cps 25 | 26 | # @return [String, nil] 27 | attr_reader :user_notice 28 | 29 | # 30 | # Initializes the policy. 31 | # 32 | # @param [String] policy 33 | # The policy text. 34 | # 35 | # @param [Hash{Symbol => Object}] qualifiers 36 | # 37 | # @option qualifiers [URI::Generic, nil] :cps 38 | # The CPS URI. 39 | # 40 | # @option qualifiers [String, nil] :user_notice 41 | # The user notice. 42 | # 43 | def initialize(policy,qualifiers={}) 44 | @policy = policy 45 | 46 | @cps = qualifiers[:cps] 47 | @user_notice = qualifiers[:user_notice] 48 | end 49 | 50 | alias to_uri cps 51 | alias to_s policy 52 | 53 | end 54 | 55 | include Enumerable 56 | 57 | # 58 | # Parses the individual policies listed in the extension's value. 59 | # 60 | # @return [Array] 61 | # 62 | def policies 63 | # XXX: ugly multiline regexp to parse the certificate policies and 64 | # their qualifiers. 65 | @policies ||= value.scan(/^Policy: [^\n]+\n(?: [^:]+: [^\n]+\n)*/m).map do |text| 66 | policy = text.match(/^Policy: ([^\n]+)/)[1] 67 | 68 | cps = if (match = text.match(/^ CPS: ([^\n]+)/m)) 69 | URI.parse(match[1]) 70 | end 71 | 72 | user_notice = if (match = text.match(/^ User Notice: ([^\n]+)/m)) 73 | match[1] 74 | end 75 | 76 | Policy.new(policy, cps: cps, user_notice: user_notice) 77 | end 78 | end 79 | 80 | # 81 | # The number of certificate policies. 82 | # 83 | # @return [Integer] 84 | # 85 | def length 86 | policies.length 87 | end 88 | 89 | # 90 | # Enumerates over every certificate policy in the extension. 91 | # 92 | # @yield [policy] 93 | # The given block will be passed each parsed policy. 94 | # 95 | # @yieldparam [Policy] policy 96 | # A parsed certificate policy. 97 | # 98 | # @return [Enumerator] 99 | # If no block is given, an Enumerator will be returned. 100 | # 101 | def each(&block) 102 | policies.each(&block) 103 | end 104 | 105 | end 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/sslyze/x509/extensions/crl_distribution_points.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/x509/extension' 2 | 3 | require 'uri' 4 | 5 | module SSLyze 6 | module X509 7 | module Extensions 8 | # 9 | # Represents the `crlDistributionPoints` X509v3 extension. 10 | # 11 | # @since 1.0.0 12 | # 13 | class CRLDistributionPoints < Extension 14 | 15 | include Enumerable 16 | 17 | # 18 | # All `URI:` values. 19 | # 20 | # @return [Array] 21 | # All parsed `URI:` values from within the extension value. 22 | # 23 | def uris 24 | @uris ||= value.scan(/URI:(.+)/).map { |(uri)| URI.parse(uri) } 25 | end 26 | 27 | # 28 | # Enumerates over each {#uris uri} value within the 29 | # `crlDistributionPoiints` extension. 30 | # 31 | # @yield [uri] 32 | # The given block will be passed each CRL URI. 33 | # 34 | # @yieldparam [URI::Generic] uri 35 | # A parsed `URI:` value from within the extension value. 36 | # 37 | # @return [Enumerator] 38 | # If no block is given, an Enumerator will be returned. 39 | # 40 | def each(&block) 41 | uris.each(&block) 42 | end 43 | 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/sslyze/x509/extensions/extended_key_usage.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/x509/extension' 2 | 3 | module SSLyze 4 | module X509 5 | module Extensions 6 | # 7 | # Represents the `extendedKeyUsage` X509v3 extension. 8 | # 9 | # @since 1.0.0 10 | # 11 | class ExtendedKeyUsage < Extension 12 | 13 | include Enumerable 14 | 15 | # 16 | # The allowed extended key uses. 17 | # 18 | # @return [Array] 19 | # 20 | def uses 21 | @uses ||= value.split(', ') 22 | end 23 | 24 | # 25 | # Enumerates over the allowed extended key uses. 26 | # 27 | # @yield [use] 28 | # 29 | # @yieldparam [String] use 30 | # 31 | # @return [Enumerator] 32 | # 33 | def each(&block) 34 | uses.each(&block) 35 | end 36 | 37 | # 38 | # Determines if TLS Web Server Authentication is allowed. 39 | # 40 | # @return [Boolean] 41 | # 42 | def tls_web_server_authentication? 43 | uses.include?('TLS Web Server Authentication') 44 | end 45 | 46 | # 47 | # Determines if TLS Web Client Authentication is allowed. 48 | # 49 | # @return [Boolean] 50 | # 51 | def tls_web_client_authentication? 52 | uses.include?('TLS Web Client Authentication') 53 | end 54 | 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/sslyze/x509/extensions/key_usage.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/x509/extension' 2 | 3 | module SSLyze 4 | module X509 5 | module Extensions 6 | # 7 | # Represents the `keyUsage` X509v3 extension. 8 | # 9 | # @since 1.0.0 10 | # 11 | class KeyUsage < Extension 12 | 13 | include Enumerable 14 | 15 | # 16 | # The various permitted key uses. 17 | # 18 | # @return [Array] 19 | # 20 | def uses 21 | @uses ||= value.split(', ') 22 | end 23 | 24 | # 25 | # @yield [use] 26 | # 27 | # @yieldparam [String] use 28 | # 29 | # @return [Enumerator] 30 | # 31 | def each(&block) 32 | uses.each(&block) 33 | end 34 | 35 | # 36 | # @return [Boolean] 37 | # 38 | def key_encipherment? 39 | uses.include?('Key Encipherment') 40 | end 41 | 42 | # 43 | # @return [Boolean] 44 | # 45 | def digital_signature? 46 | uses.include?('Digital Signature') 47 | end 48 | 49 | # 50 | # @return [Boolean] 51 | # 52 | def crl_sign? 53 | uses.include?('CRL Sign') 54 | end 55 | 56 | # 57 | # @return [Boolean] 58 | # 59 | def certificate_sign? 60 | uses.include?('Certificate Sign') 61 | end 62 | 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/sslyze/x509/extensions/subject_alt_name.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/x509/extension' 2 | require 'sslyze/x509/domain' 3 | 4 | require 'uri' 5 | require 'ipaddr' 6 | 7 | module SSLyze 8 | module X509 9 | module Extensions 10 | # 11 | # Represents the `subjectAltName` X509v3 extension. 12 | # 13 | # @since 1.0.0 14 | # 15 | class SubjectAltName < Extension 16 | 17 | include Enumerable 18 | 19 | # Known subject name types. 20 | TYPES = { 21 | 'DNS' => :dns, 22 | 'IP' => :ip, 23 | 'URI' => :uri, 24 | 'RID' => :rid, 25 | 26 | 'email' => :email, 27 | 'dirName' => :dir_name, 28 | 'otherName' => :other_name 29 | } 30 | 31 | # 32 | # Enumerates over every alternative name within the extension's value. 33 | # 34 | # @yield [type, name] 35 | # The given block will be passed each 36 | # 37 | # @yieldparam [:dns, :ip, :uri, :rid, :email, :dir_name, :other_name] type 38 | # The type of the alternative name being yielded. 39 | # 40 | # @yieldparam [String] name 41 | # An alternative name within the extension's value. 42 | # 43 | # @return [Enumerator] 44 | # If no block is given, an Enumerator will be returned. 45 | # 46 | # @raise [NotImplementedError] 47 | # An unknown name type was encountered while parsing the extension's 48 | # value. 49 | # 50 | def each 51 | return enum_for unless block_given? 52 | 53 | value.split(', ').each do |type_value| 54 | type, value = type_value.split(':',2) 55 | 56 | unless TYPES.has_key?(type) 57 | raise(NotImplementedError,"unsupported subjectAltName type: #{type}") 58 | end 59 | 60 | yield TYPES[type], value 61 | end 62 | end 63 | 64 | # 65 | # All `DNS:` alternative names within the extension's value. 66 | # 67 | # @return [Array] 68 | # 69 | def dns 70 | @dns ||= select { |type,value| type == :dns }.map do |(type,value)| 71 | value 72 | end 73 | end 74 | 75 | # 76 | # All `IP:` alternative names within the extension's value. 77 | # 78 | # @return [Array] 79 | # 80 | def ip 81 | @ip ||= select { |type,value| type == :ip }.map do |(type,value)| 82 | IPAddr.new(value) 83 | end 84 | end 85 | 86 | # 87 | # All `URI:` alternative names within the extension's value. 88 | # 89 | # @return [Array] 90 | # 91 | def uri 92 | @uri ||= select { |type,value| type == :uri }.map do |(type,value)| 93 | URI.parse(value) 94 | end 95 | end 96 | 97 | # 98 | # All `email:` alternative names within the extension's value. 99 | # 100 | # @return [Array] 101 | # 102 | def email 103 | @email ||= select { |type,value| type == :email }.map do |(type,value)| 104 | value 105 | end 106 | end 107 | 108 | # 109 | # All `RID:` alternative names within the extension's value. 110 | # 111 | # @return [Array] 112 | # 113 | def rid 114 | @rid ||= select { |type,value| type == :rid }.map do |(type,value)| 115 | value 116 | end 117 | end 118 | 119 | # 120 | # All `dirName:` alternative names within the extension's value. 121 | # 122 | # @return [Array] 123 | # 124 | def dir_name 125 | @dir_name ||= select { |type,value| type == :dir_name }.map do |(type,value)| 126 | value 127 | end 128 | end 129 | 130 | # 131 | # All `otherName:` alternative names within the extension's value. 132 | # 133 | # @return [Array] 134 | # 135 | def other_name 136 | @other_name ||= select { |type,value| type == :other_name }.map do |(type,value)| 137 | value 138 | end 139 | end 140 | 141 | end 142 | end 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /lib/sslyze/x509/name.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/x509/domain' 2 | 3 | require 'openssl' 4 | 5 | module SSLyze 6 | module X509 7 | # 8 | # Wrapper object for [OpenSSL::X509::Name][1]. 9 | # 10 | # [1]: http://www.rubydoc.info/stdlib/openssl/OpenSSL/X509/Name 11 | # 12 | # @since 1.0.0 13 | # 14 | class Name 15 | 16 | include Enumerable 17 | 18 | # 19 | # The parsed entries of the name. 20 | # 21 | # @return [Array<(String, String, Integer)>] 22 | # 23 | attr_reader :entries 24 | 25 | # 26 | # @param [OpenSSL::X509::Name] name 27 | # The OpenSSL X509 name object. 28 | # 29 | def initialize(name) 30 | @name = name 31 | @entries = name.to_a 32 | end 33 | 34 | # 35 | # Enumerates over the entries. 36 | # 37 | # @yield [oid, value, type] 38 | # 39 | # @yieldparam [String] oid 40 | # The Object IDentifier. 41 | # 42 | # @yieldparam [String] value 43 | # The entry's value. 44 | # 45 | # @yieldparam [Integer] type 46 | # The entry type. 47 | # 48 | def each(&block) 49 | @entries.each do |(oid,value,type)| 50 | yield oid, value, type 51 | end 52 | end 53 | 54 | # 55 | # Finds the entry with the matcing OID (Object IDentifier). 56 | # 57 | # @param [String] key 58 | # 59 | # @return [String, nil] 60 | # 61 | def [](key) 62 | each do |oid,value,type| 63 | return value if oid == key 64 | end 65 | 66 | return nil 67 | end 68 | 69 | # 70 | # The Country (`C`) entry. 71 | # 72 | # @return [String] 73 | # 74 | def country_name 75 | @country_name ||= self['C'] 76 | end 77 | 78 | alias country country_name 79 | alias c country_name 80 | 81 | # 82 | # The Common Name (`CN`) entry. 83 | # 84 | # @return [Domain] 85 | # 86 | def common_name 87 | @common_name ||= Domain.new(self['CN']) 88 | end 89 | 90 | alias cn common_name 91 | 92 | # 93 | # The Domain Component (`DC`) entry. 94 | # 95 | # @return [String, nil] 96 | # 97 | def domain_component 98 | @domain_component ||= self['DC'] 99 | end 100 | 101 | alias dc domain_component 102 | 103 | # 104 | # The Organization Name (`O`) entry. 105 | # 106 | # @return [String] 107 | # 108 | def organization_name 109 | @organization_name ||= self['O'] 110 | end 111 | 112 | alias organization organization_name 113 | alias o organization_name 114 | 115 | # 116 | # The Organization Unit Name (`OU`) entry. 117 | # 118 | # @return [String] 119 | # 120 | def organizational_unit_name 121 | @organizational_unit_name ||= self['OU'] 122 | end 123 | 124 | alias organizational_unit organizational_unit_name 125 | alias ou organizational_unit_name 126 | 127 | # 128 | # The State/Province Name (`ST`) entry. 129 | # 130 | # @return [String, nil] 131 | # 132 | def state_name 133 | @state_name ||= self['ST'] 134 | end 135 | 136 | alias state state_name 137 | alias province_name state_name 138 | alias province province_name 139 | alias st state_name 140 | 141 | # 142 | # The Location (`L`) entry. 143 | # 144 | # @return [String, nil] 145 | # 146 | def location_name 147 | @location ||= self['L'] 148 | end 149 | 150 | alias location location_name 151 | alias l location_name 152 | 153 | # 154 | # @see http://www.rubydoc.info/stdlib/openssl/OpenSSL/X509/Name#cmp-instance_method 155 | # 156 | def cmp(other) 157 | @name.cmp(other.name) 158 | end 159 | 160 | # 161 | # @see http://www.rubydoc.info/stdlib/openssl/OpenSSL/X509/Name#eql%3F-instance_method 162 | # 163 | def eql?(other) 164 | @name.eql?(other.name) 165 | end 166 | 167 | # 168 | # @see http://www.rubydoc.info/stdlib/openssl/OpenSSL/X509/Name#to_a-instance_method 169 | # 170 | def to_a 171 | @name.to_a 172 | end 173 | 174 | # 175 | # @see http://www.rubydoc.info/stdlib/openssl/OpenSSL/X509/Name#to_der-instance_method 176 | # 177 | def to_der 178 | @name.to_der 179 | end 180 | 181 | # 182 | # @see http://www.rubydoc.info/stdlib/openssl/OpenSSL/X509/Name#to_s-instance_method 183 | # 184 | def to_s(*args) 185 | @name.to_s(*args) 186 | end 187 | 188 | protected 189 | 190 | attr_reader :name 191 | 192 | end 193 | end 194 | end 195 | -------------------------------------------------------------------------------- /lib/sslyze/xml.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/target' 2 | require 'sslyze/xml/invalid_target' 3 | require 'sslyze/xml/types' 4 | require 'sslyze/xml/attributes/title' 5 | 6 | require 'nokogiri' 7 | 8 | module SSLyze 9 | # 10 | # Represents the XML output from sslyze. 11 | # 12 | # @see https://github.com/nabla-c0d3/sslyze/blob/master/xml_out.xsd 13 | # 14 | class XML 15 | 16 | include Types 17 | include Attributes::Title 18 | 19 | # 20 | # Initializes the XML. 21 | # 22 | # @param [Nokogiri::XML::Document] doc 23 | # The XML document. 24 | # 25 | def initialize(doc) 26 | @doc = doc 27 | end 28 | 29 | # 30 | # Parses the XML. 31 | # 32 | # @param [String] xml 33 | # The raw XML. 34 | # 35 | # @return [XML] 36 | # 37 | def self.parse(xml) 38 | new(Nokogiri::XML.parse(xml)) 39 | end 40 | 41 | # 42 | # Opens the XML file. 43 | # 44 | # @param [String] path 45 | # Path to the XML file. 46 | # 47 | # @return [XML] 48 | # 49 | def self.open(path) 50 | new(File.open(path) { |file| Nokogiri::XML(file) }) 51 | end 52 | 53 | # 54 | # The version of the XML output. 55 | # 56 | # @return [String] 57 | # 58 | def version 59 | @version ||= @doc.at_xpath('/document/@SSLyzeVersion').value 60 | end 61 | 62 | # 63 | # The default timeout used. 64 | # 65 | # @return [Integer, nil] 66 | # 67 | # @since 1.0.0 68 | # 69 | def network_timeout 70 | @default_time ||= if (attr = @doc.at_xpath('/document/results/@networkTimeout')) 71 | attr.value.to_i 72 | end 73 | end 74 | 75 | # 76 | # Duration of the scan. 77 | # 78 | # @return [Float, nil] 79 | # 80 | def total_scan_time 81 | @total_scan_time ||= if (attr = @doc.at_xpath('/document/results/@totalScanTime')) 82 | attr.value.to_f 83 | end 84 | end 85 | 86 | # Enumerates over each invalid target. 87 | # 88 | # @yield [invalidtarget] 89 | # 90 | # @yieldparam [InvalidTarget] invalid_target 91 | # 92 | # @return [Enumerator] 93 | # 94 | # @since 0.2.0 95 | # 96 | def each_invalid_target 97 | return enum_for(__method__) unless block_given? 98 | 99 | @doc.xpath('/document/invalidTargets/invalidTarget').each do |inval| 100 | yield InvalidTarget.new(inval) 101 | end 102 | end 103 | 104 | # 105 | # @return [Array] 106 | # 107 | # @see #each_invalid_target 108 | # 109 | # @since 0.2.0 110 | # 111 | def invalid_targets 112 | each_invalid_target.to_a 113 | end 114 | 115 | # 116 | # Enumerates over each target. 117 | # 118 | # @yield [target] 119 | # 120 | # @yieldparam [Target] target 121 | # 122 | # @return [Enumerator] 123 | # 124 | def each_target 125 | return enum_for(__method__) unless block_given? 126 | 127 | @doc.xpath('/document/results/target').each do |target| 128 | yield Target.new(target) 129 | end 130 | end 131 | 132 | alias each each_target 133 | 134 | # 135 | # @return [Array] 136 | # 137 | # @see #each_target 138 | # 139 | def targets 140 | each_target.to_a 141 | end 142 | 143 | # 144 | # The first target. 145 | # 146 | # @return [Target, nil] 147 | # 148 | def target 149 | each_target.first 150 | end 151 | 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /lib/sslyze/xml/attributes.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/attributes/title' 2 | require 'sslyze/xml/attributes/is_supported' 3 | require 'sslyze/xml/attributes/is_vulnerable' 4 | require 'sslyze/xml/attributes/exception' 5 | require 'sslyze/xml/attributes/error' 6 | -------------------------------------------------------------------------------- /lib/sslyze/xml/attributes/error.rb: -------------------------------------------------------------------------------- 1 | module SSLyze 2 | class XML 3 | module Attributes 4 | # 5 | # Provides methods for parsing the `error` XML attribute. 6 | # 7 | # @since 1.0.0 8 | # 9 | module Error 10 | # 11 | # The error message, if an error occurred. 12 | # 13 | # @return [String, nil] 14 | # 15 | def error 16 | @error ||= @node['error'] 17 | end 18 | 19 | # 20 | # Determines if an error occurred. 21 | # 22 | # @return [Boolean] 23 | # 24 | def error? 25 | !error.nil? 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/sslyze/xml/attributes/exception.rb: -------------------------------------------------------------------------------- 1 | module SSLyze 2 | class XML 3 | module Attributes 4 | # 5 | # Provides methods for accessing the `exception` XML attribute. 6 | # 7 | # @since 1.0.0 8 | # 9 | module Exception 10 | # 11 | # The exception message, if an exception occurred. 12 | # 13 | # @return [String, nil] 14 | # 15 | def exception 16 | @exception ||= @node['exception'] 17 | end 18 | 19 | # 20 | # Tests whether an exception occurred. 21 | # 22 | # @return [Boolean] 23 | # 24 | def exception? 25 | !exception.nil? 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/sslyze/xml/attributes/is_supported.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/types' 2 | 3 | module SSLyze 4 | class XML 5 | module Attributes 6 | # 7 | # Common methods for the `isSupported` attribute. 8 | # 9 | # @since 1.0.0 10 | # 11 | module IsSupported 12 | 13 | include Types 14 | 15 | # 16 | # Parses the `isSupported` attribute. 17 | # 18 | # @return [Boolean] 19 | # 20 | def is_supported? 21 | Boolean[@node['isSupported']] 22 | end 23 | 24 | alias supported? is_supported? 25 | 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/sslyze/xml/attributes/is_vulnerable.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/types' 2 | 3 | module SSLyze 4 | class XML 5 | module Attributes 6 | # 7 | # Common methods for the `isVulnerable` attribute. 8 | # 9 | # @since 1.0.0 10 | # 11 | module IsVulnerable 12 | 13 | include Types 14 | 15 | # 16 | # Parses the `isVulnerable` attribute. 17 | # 18 | # @return [Boolean] 19 | # 20 | def is_vulnerable? 21 | Boolean[@node['isVulnerable']] 22 | end 23 | 24 | alias vulnerable? is_vulnerable? 25 | 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/sslyze/xml/attributes/title.rb: -------------------------------------------------------------------------------- 1 | module SSLyze 2 | class XML 3 | module Attributes 4 | # 5 | # Provides methods for accessing the `title` XML attribute. 6 | # 7 | # @since 1.0.0 8 | # 9 | module Title 10 | # 11 | # The title. 12 | # 13 | # @return [String, nil] 14 | # The value of the `title` attribute. 15 | # 16 | def title 17 | @title ||= @node['title'] 18 | end 19 | 20 | # 21 | # The title or an empty String. 22 | # 23 | # @return [String] 24 | # 25 | def to_s 26 | title || '' 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/sslyze/xml/certinfo.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/certinfo/received_certificate_chain' 3 | require 'sslyze/xml/certinfo/certificate_validation' 4 | require 'sslyze/xml/certinfo/certificate_validation/verified_certificate_chain' 5 | require 'sslyze/xml/certinfo/ocsp_stapling' 6 | 7 | module SSLyze 8 | class XML 9 | # 10 | # Represents the `` XML element. 11 | # 12 | # @since 1.0.0 13 | # 14 | class Certinfo < Plugin 15 | 16 | # 17 | # The received certificate chain. 18 | # 19 | # @return [ReceivedCertificateChain] 20 | # 21 | def received_certificate_chain 22 | @received_certificate_chain ||= ReceivedCertificateChain.new( 23 | @node.at_xpath('receivedCertificateChain') 24 | ) 25 | end 26 | 27 | alias received_chain received_certificate_chain 28 | 29 | # 30 | # Certificate validation information. 31 | # 32 | # @return [CertificateValidation] 33 | # 34 | def certificate_validation 35 | @certificate_validation ||= CertificateValidation.new( 36 | @node.at_xpath('certificateValidation') 37 | ) 38 | end 39 | 40 | alias validation certificate_validation 41 | 42 | # 43 | # The verified certificate chain. 44 | # 45 | # @return [VerifiedCertificateChain, nil] 46 | # 47 | def verified_certificate_chain 48 | @verified_certificate_chain ||= if (element = @node.at_xpath('certificateValidation/verifiedCertificateChain')) 49 | CertificateValidation::VerifiedCertificateChain.new(element) 50 | 51 | end 52 | end 53 | 54 | alias verified_chain verified_certificate_chain 55 | 56 | # 57 | # OCSP Stapling. 58 | # 59 | # @return [OCSPStapling] 60 | # 61 | def ocsp_stapling 62 | @ocsp_stapling ||= OCSPStapling.new(@node.at_xpath('ocspStapling')) 63 | end 64 | 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/sslyze/xml/certinfo/certificate.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/certinfo/certificate/public_key' 3 | require 'sslyze/x509/name' 4 | require 'sslyze/x509/extension_set' 5 | 6 | require 'openssl' 7 | 8 | module SSLyze 9 | class XML 10 | class Certinfo < Plugin 11 | # 12 | # Represents the `` XML element. 13 | # 14 | class Certificate 15 | 16 | # 17 | # Initializes the certificate. 18 | # 19 | # @param [Nokogiri::XML::Node] node 20 | # The `` XML element. 21 | # 22 | def initialize(node) 23 | @node = node 24 | end 25 | 26 | # 27 | # The AS PEM information. 28 | # 29 | # @return [String] 30 | # 31 | def as_pem 32 | @as_pem ||= @node.at_xpath('asPEM').inner_text 33 | end 34 | 35 | alias to_s as_pem 36 | 37 | # 38 | # The parsed X509 certificate. 39 | # 40 | # @return [OpenSSL::X509::Certificate] 41 | # 42 | # @see http://www.rubydoc.info/stdlib/openssl/OpenSSL/X509/Certificate 43 | # 44 | # @since 1.0.0 45 | # 46 | def x509 47 | @x509 ||= OpenSSL::X509::Certificate.new(as_pem) 48 | end 49 | 50 | # 51 | # @return [X509::ExtensionSet] 52 | # 53 | # @group OpenSSL Methods 54 | # 55 | def extensions 56 | X509::ExtensionSet.new(x509.extensions) 57 | end 58 | 59 | # 60 | # @return [X509::Name] 61 | # 62 | # @see http://www.rubydoc.info/stdlib/openssl/OpenSSL/X509/Name 63 | # 64 | # @group OpenSSL Methods 65 | # 66 | def issuer 67 | @issuer ||= X509::Name.new(x509.issuer) 68 | end 69 | 70 | # 71 | # @return [Time] 72 | # 73 | # @group OpenSSL Methods 74 | # 75 | def not_after 76 | x509.not_after 77 | end 78 | 79 | # 80 | # @return [Time] 81 | # 82 | # @group OpenSSL Methods 83 | # 84 | def not_before 85 | x509.not_before 86 | end 87 | 88 | # 89 | # @return [PublicKey] 90 | # 91 | # @group OpenSSL Methods 92 | # 93 | def public_key 94 | @public_key ||= PublicKey.new(@node.at_xpath('publicKey')) 95 | end 96 | 97 | # 98 | # @return [OpenSSL::BN] 99 | # 100 | # @see http://www.rubydoc.info/stdlib/openssl/OpenSSL/BN 101 | # 102 | # @group OpenSSL Methods 103 | # 104 | def serial 105 | x509.serial 106 | end 107 | 108 | # 109 | # @return [String] 110 | # 111 | # @group OpenSSL Methods 112 | # 113 | def signature_algorithm 114 | x509.signature_algorithm 115 | end 116 | 117 | # 118 | # @return [X509::Name] 119 | # 120 | # @group OpenSSL Methods 121 | # 122 | def subject 123 | @subject ||= X509::Name.new(x509.subject) 124 | end 125 | 126 | # 127 | # @return [String] 128 | # 129 | # @group OpenSSL Methods 130 | # 131 | def to_der 132 | x509.to_der 133 | end 134 | 135 | # 136 | # @return [String] 137 | # 138 | # @group OpenSSL Methods 139 | # 140 | def to_text 141 | x509.to_text 142 | end 143 | 144 | # 145 | # @return [Integer] 146 | # 147 | # @group OpenSSL Methods 148 | # 149 | def version 150 | x509.version 151 | end 152 | 153 | # 154 | # The SHA1 fingerprint of the cert. 155 | # 156 | # @return [String] 157 | # 158 | def sha1_fingerprint 159 | @sha1_fingerprint ||= @node['sha1Fingerprint'] 160 | end 161 | 162 | # 163 | # The HPKP SHA256 Pin. 164 | # 165 | # @return [String] 166 | # 167 | # @since 1.0.0 168 | # 169 | def hpkp_sha256_pin 170 | @hpkp_sha256_pin ||= @node['hpkpSha256Pin'] 171 | end 172 | 173 | # 174 | # The supplied server name indication. 175 | # 176 | # @return [String] 177 | # 178 | # @since 1.0.0 179 | # 180 | def supplied_server_name_indication 181 | @supplied_server_name_indication ||= @node['suppliedServerNameIndication'] 182 | end 183 | 184 | # 185 | # Compares the other certificiate to this certificate. 186 | # 187 | # @param [Certificate] other 188 | # The other certificate. 189 | # 190 | # @return [Boolean] 191 | # Whether the other certificate has the same {#as_pem}. 192 | # 193 | # @since 1.0.0 194 | # 195 | def ==(other) 196 | other.kind_of?(self.class) && other.as_pem == as_pem 197 | end 198 | 199 | end 200 | end 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /lib/sslyze/xml/certinfo/certificate/public_key.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | 3 | module SSLyze 4 | class XML 5 | class Certinfo < Plugin 6 | class Certificate 7 | # 8 | # @since 1.2.0 9 | # 10 | class PublicKey 11 | 12 | ALGORITHMS = { 13 | 'RSA' => :RSA, 14 | 'DSA' => :DSA, 15 | 'EllipticCurve' => :EC 16 | } 17 | 18 | # 19 | # Initializes the public-key information. 20 | # 21 | # @param [Nokogiri::XML::Node] node 22 | # 23 | def initialize(node) 24 | @node = node 25 | end 26 | 27 | # 28 | # The algorithm used to generate the public-key. 29 | # 30 | # @return [:RSA, :DSA, :EC] 31 | # 32 | # @raise [NotImplementedError] 33 | # Unrecognized algorithm name. 34 | # 35 | def algorithm 36 | unless @algorithm 37 | name = @node['algorithm'] 38 | 39 | @algorithm = ALGORITHMS.fetch(name) do 40 | raise(notimplementederror,"unknown public-key algorithm: #{name.inspect}") 41 | end 42 | end 43 | 44 | return @algorithm 45 | end 46 | 47 | # 48 | # The size of the public-key. 49 | # 50 | # @return [Integer] 51 | # The size in bits. 52 | # 53 | def size 54 | @size ||= @node['size'].to_i 55 | end 56 | 57 | # 58 | # The Elliptical Curve that was used. 59 | # 60 | # @return [Symbol, nil] 61 | # The Elliptical Curve, or `nil` if {#algorithm} was not `:EC`. 62 | # 63 | def curve 64 | @curve ||= if (curve = @node['curve']) 65 | curve.to_sym 66 | end 67 | end 68 | 69 | # 70 | # The exponent used to generate the public-key 71 | # 72 | # @return [Integer, nil] 73 | # 74 | def exponent 75 | @exponent ||= if (exponent = @node['exponent']) 76 | exponent.to_i 77 | end 78 | end 79 | 80 | end 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/sslyze/xml/certinfo/certificate_validation.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/certinfo/certificate_validation/hostname_validation' 3 | require 'sslyze/xml/certinfo/certificate_validation/path_validation' 4 | 5 | module SSLyze 6 | class XML 7 | class Certinfo < Plugin 8 | # 9 | # Represents the `` XML element. 10 | # 11 | # @since 1.0.0 12 | # 13 | class CertificateValidation 14 | 15 | # 16 | # Initializes the {CertificateValidation} object. 17 | # 18 | # @param [Nokogiri::XML::Element] node 19 | # The `` XML element. 20 | # 21 | def initialize(node) 22 | @node = node 23 | end 24 | 25 | # 26 | # Hostname based validation information. 27 | # 28 | # @return [HostnameValidation] 29 | # 30 | def hostname_validation 31 | @hostname_validation ||= HostnameValidation.new( 32 | @node.at_xpath('hostnameValidation') 33 | ) 34 | end 35 | 36 | alias hostname hostname_validation 37 | 38 | # 39 | # Enumerates over the path-based validation information. 40 | # 41 | # @yield [path_validation] 42 | # 43 | # @yieldparam [PathValidation] path_validation 44 | # 45 | # @return [Enumerator] 46 | # 47 | def each_path_validation 48 | return enum_for(__method__) unless block_given? 49 | 50 | @node.xpath('pathValidation').each do |element| 51 | yield PathValidation.new(element) 52 | end 53 | end 54 | 55 | # 56 | # @return [Array] 57 | # 58 | # @see #each_path_validation 59 | # 60 | def path_validations 61 | each_path_validation.to_a 62 | end 63 | 64 | alias path path_validations 65 | 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/sslyze/xml/certinfo/certificate_validation/hostname_validation.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | 4 | module SSLyze 5 | class XML 6 | class Certinfo < Plugin 7 | class CertificateValidation 8 | # 9 | # Represents the `` XML element. 10 | # 11 | # @since 1.0.0 12 | # 13 | class HostnameValidation 14 | 15 | include Types 16 | 17 | # 18 | # Initializes the element. 19 | # 20 | # @param [Nokogiri::XML::Element] node 21 | # The `` XML element. 22 | # 23 | def initialize(node) 24 | @node = node 25 | end 26 | 27 | # 28 | # Determines if the certificate Common Name matches the target domain 29 | # name. 30 | # 31 | # @return [Boolean] 32 | # 33 | def certificate_matches_server_hostname? 34 | Boolean[@node['certificateMatchesServerHostname']] 35 | end 36 | 37 | alias matches_server_hostname? certificate_matches_server_hostname? 38 | 39 | # 40 | # The server's domain name. 41 | # 42 | # @return [String] 43 | # 44 | def server_hostname 45 | @node['serverHostname'] 46 | end 47 | 48 | alias to_s server_hostname 49 | 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/sslyze/xml/certinfo/certificate_validation/path_validation.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | require 'sslyze/xml/attributes/error' 4 | 5 | module SSLyze 6 | class XML 7 | class Certinfo < Plugin 8 | class CertificateValidation 9 | # 10 | # Represents the `` XML element. 11 | # 12 | # @since 1.0.0 13 | # 14 | class PathValidation 15 | 16 | include Types 17 | include Attributes::Error 18 | 19 | # 20 | # Initializes the element. 21 | # 22 | # @param [Nokogiri::XML::Element] node 23 | # The `` XML element. 24 | # 25 | def initialize(node) 26 | @node = node 27 | end 28 | 29 | # 30 | # @return [Boolean, nil] 31 | # 32 | def is_extended_validation_certificate? 33 | Boolean[@node['isExtendedValidationCertificate']] 34 | end 35 | 36 | alias is_extended_validation_cert? is_extended_validation_certificate? 37 | 38 | # 39 | # @return [String] 40 | # 41 | def trust_store_version 42 | @trust_store_version ||= @node['trustStoreVersion'] 43 | end 44 | 45 | # 46 | # @return [String] 47 | # 48 | def using_trust_store 49 | @using_trust_store ||= @node['usingTrustStore'] 50 | end 51 | 52 | alias trust_store using_trust_store 53 | 54 | # 55 | # The validation result. 56 | # 57 | # @return [Symbol, nil] 58 | # 59 | def validation_result 60 | @validation_result ||= if (value = @node['validationResult']) 61 | value.to_sym 62 | end 63 | end 64 | 65 | alias result validation_result 66 | 67 | # 68 | # Determines if the {#validation_result} was `:ok`. 69 | # 70 | # @return [Boolean, nil] 71 | # 72 | def ok? 73 | if validation_result 74 | validation_result == :ok 75 | end 76 | end 77 | 78 | alias valid? ok? 79 | 80 | end 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/sslyze/xml/certinfo/certificate_validation/verified_certificate_chain.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/certinfo/has_certificates' 3 | 4 | module SSLyze 5 | class XML 6 | class Certinfo < Plugin 7 | class CertificateValidation 8 | # 9 | # Represents the `` XML element. 10 | # 11 | # @since 1.0.0 12 | # 13 | class VerifiedCertificateChain 14 | 15 | include Types 16 | include HasCertificates 17 | 18 | # 19 | # Initializes the {VerifiedCertificateChain} object. 20 | # 21 | # @param [Nokogiri::XML::Element] node 22 | # The `` XML element. 23 | # 24 | def initialize(node) 25 | @node = node 26 | end 27 | 28 | # 29 | # Parses the `hasSha1SignedCertificate` XML attribute. 30 | # 31 | # @return [Boolean] 32 | # 33 | def has_sha1_signed_certificate? 34 | Boolean[@node['hasSha1SignedCertificate']] 35 | end 36 | 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/sslyze/xml/certinfo/has_certificates.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | require 'sslyze/xml/certinfo/certificate' 4 | 5 | module SSLyze 6 | class XML 7 | class Certinfo < Plugin 8 | # 9 | # @since 1.0.0 10 | # 11 | module HasCertificates 12 | include Enumerable 13 | 14 | # 15 | # Enumerates over each certificate in the chain. 16 | # 17 | # @yield [cert] 18 | # The given block will be passed each certificate. 19 | # 20 | # @yieldparam [Certificate] cert 21 | # A certificate in the chain. 22 | # 23 | # @return [Enumerator] 24 | # If no block was given, an Enumerator will be returned. 25 | # 26 | def each_certificate 27 | return enum_for(__method__) unless block_given? 28 | 29 | @node.xpath('certificate').each do |element| 30 | yield Certificate.new(element) 31 | end 32 | end 33 | 34 | alias each_cert each_certificate 35 | alias each each_certificate 36 | 37 | # 38 | # Returns all certificates in the chain. 39 | # 40 | # @return [Array] 41 | # 42 | def certificates 43 | each_certificate.to_a 44 | end 45 | 46 | alias certs certificates 47 | 48 | # 49 | # The leaf certificate. 50 | # 51 | # @return [Certificate, nil] 52 | # 53 | def leaf 54 | if (element = @node.at_xpath('certificate[1]')) 55 | Certificate.new(element) 56 | end 57 | end 58 | 59 | # 60 | # Enumerates over any intermediate certificates in the chain. 61 | # 62 | # @yield [cert] 63 | # The given block will be passed each intermediate certificate. 64 | # 65 | # @yieldparam [Certificate] cert 66 | # An intermediate certificate in the chain. 67 | # 68 | # @return [Enumerator] 69 | # If no block was given, an Enumerator will be returned. 70 | # 71 | def each_intermediate 72 | return enum_for(__method__) unless block_given? 73 | 74 | @node.xpath('certificate[position() > 1 and position() < last()]').each do |element| 75 | yield Certificate.new(element) 76 | end 77 | end 78 | 79 | # 80 | # Returns all intermediate certificates in the chain. 81 | # 82 | # @return [Array] 83 | # 84 | def intermediates 85 | each_intermediate.to_a 86 | end 87 | 88 | # 89 | # The root certificate. 90 | # 91 | # @return [Certificate, nil] 92 | # 93 | def root 94 | if (element = @node.at_xpath('certificate[last()]')) 95 | Certificate.new(element) 96 | end 97 | end 98 | 99 | end 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/sslyze/xml/certinfo/ocsp_stapling.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/attributes/is_supported' 3 | require 'sslyze/xml/certinfo/ocsp_stapling/ocsp_response' 4 | 5 | module SSLyze 6 | class XML 7 | class Certinfo < Plugin 8 | # 9 | # Represents the `` XML element. 10 | # 11 | # @since 1.0.0 12 | # 13 | class OCSPStapling 14 | 15 | include Attributes::IsSupported 16 | 17 | # 18 | # Initializes the {OCSPStapling} object. 19 | # 20 | # @param [Nokogiri::XML::Element] node 21 | # The `` XML element. 22 | # 23 | def initialize(node) 24 | @node = node 25 | end 26 | 27 | # 28 | # The OCSP Response. 29 | # 30 | # @return [OCSPResponse, nil] 31 | # 32 | # @note Parses the `` XML element. 33 | # 34 | def ocsp_response 35 | @ocsp_response ||= if (element = @node.at_xpath('ocspResponse')) 36 | OCSPResponse.new(element) 37 | end 38 | end 39 | 40 | alias response ocsp_response 41 | 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/sslyze/xml/certinfo/ocsp_stapling/ocsp_response.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | 4 | require 'time' 5 | 6 | module SSLyze 7 | class XML 8 | class Certinfo < Plugin 9 | class OCSPStapling 10 | # 11 | # Represents the `` XML element. 12 | # 13 | # @since 1.0.0 14 | # 15 | class OCSPResponse 16 | 17 | include Types 18 | 19 | # 20 | # Initializes the {OCSPResponse} object. 21 | # 22 | # @param [Nokogiri::XML::Element] node 23 | # The `` XML element. 24 | # 25 | def initialize(node) 26 | @node = node 27 | end 28 | 29 | # 30 | # The responder's ID. 31 | # 32 | # @return [String] 33 | # 34 | # @note Parses the `responderID` attribute. 35 | # 36 | def responder_id 37 | @responder_id ||= @node.at_xpath('responderID').inner_text 38 | end 39 | 40 | alias id responder_id 41 | 42 | # 43 | # When the response was produced at. 44 | # 45 | # @return [Time] 46 | # 47 | def produced_at 48 | @produced_at ||= Time.parse(@node.at_xpath('producedAt').inner_text) 49 | end 50 | 51 | alias to_time produced_at 52 | 53 | # 54 | # The status. 55 | # 56 | # @return [:successful] 57 | # 58 | def status 59 | @status ||= @node['status'].downcase.to_sym 60 | end 61 | 62 | # 63 | # Determines if the response status was successful. 64 | # 65 | # @return [Boolean] 66 | # 67 | def successful? 68 | status == :successful 69 | end 70 | 71 | # 72 | # Specifies whether the OCSP Response is trusted by Mozilla's CA 73 | # Store. 74 | # 75 | # @return [Boolean] 76 | # 77 | def is_trusted_by_mozilla_ca_store? 78 | Boolean[@node['isTrustedByMozillaCAStore']] 79 | end 80 | 81 | alias trusted? is_trusted_by_mozilla_ca_store? 82 | 83 | end 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/sslyze/xml/certinfo/received_certificate_chain.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | require 'sslyze/xml/certinfo/has_certificates' 4 | 5 | module SSLyze 6 | class XML 7 | class Certinfo < Plugin 8 | # 9 | # Represents the `` XML element. 10 | # 11 | # @since 1.0.0 12 | # 13 | class ReceivedCertificateChain 14 | 15 | include Types 16 | include HasCertificates 17 | 18 | # 19 | # Initializes the {ReceivedCertificateChain} object. 20 | # 21 | # @param [Nokogiri::XML::Element] node 22 | # 23 | def initialize(node) 24 | @node = node 25 | end 26 | 27 | # 28 | # Parses the `isChainOrderValid` XML attribute. 29 | # 30 | # @return [Boolean] 31 | # 32 | def is_chain_order_valid? 33 | Boolean[@node['isChainOrderValid']] 34 | end 35 | 36 | # 37 | # Parses the `containsAnchorCertificate` XML attribute. 38 | # 39 | # @return [Boolean] 40 | # 41 | def contains_anchor_certificate? 42 | Boolean[@node['containsAnchorCertificate']] 43 | end 44 | 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/sslyze/xml/compression.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/compression/compression_method' 3 | 4 | module SSLyze 5 | class XML 6 | # 7 | # Represents the `` XML element. 8 | # 9 | # @since 1.0.0 10 | # 11 | class Compression < Plugin 12 | 13 | # 14 | # Parses the `` XML element. 15 | # 16 | # @return [CompressionMethod] 17 | # 18 | def deflate 19 | @compression_method ||= CompressionMethod.new( 20 | @node.at_xpath('compressionMethod[@type="DEFLATE"]') 21 | ) 22 | end 23 | 24 | # 25 | # @see CompressionMethod#is_supported? 26 | # 27 | def deflate? 28 | deflate.is_supported? 29 | end 30 | 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/sslyze/xml/compression/compression_method.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/attributes/is_supported' 3 | 4 | module SSLyze 5 | class XML 6 | class Compression < Plugin 7 | # 8 | # Represents the `` XML element. 9 | # 10 | # @since 1.0.0 11 | # 12 | class CompressionMethod 13 | 14 | include Attributes::IsSupported 15 | 16 | # 17 | # Initializes the {CompressionMethod} object. 18 | # 19 | # @param [Nokogiri::XML::Element] node 20 | # The `` XML element. 21 | # 22 | def initialize(node) 23 | @node = node 24 | end 25 | 26 | # 27 | # The type of compression. 28 | # 29 | # @return [Symbol] 30 | # 31 | def type 32 | @type ||= @node['type'].to_sym 33 | end 34 | 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/sslyze/xml/fallback.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | 3 | module SSLyze 4 | class XML 5 | # 6 | # Represents the `` XML element. 7 | # 8 | # @since 1.0.0 9 | # 10 | class Fallback < Plugin 11 | 12 | # 13 | # Parses the `` XML element. 14 | # 15 | # @return [TLSFallbackSCSV] 16 | # 17 | def tls_fallback_scsv 18 | @tls_fallback_scsv ||= TLSFallbackSCSV.new( 19 | @node.at_xpath('tlsFallbackScsv') 20 | ) 21 | end 22 | 23 | # 24 | # @see TLSFallbackSCSV#is_supported? 25 | # 26 | def is_supported? 27 | tls_fallback_scsv.is_supported? 28 | end 29 | 30 | alias supported? is_supported? 31 | 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/sslyze/xml/fallback/tls_fallback_scsv.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/is_supported' 3 | 4 | module SSLyze 5 | class XML 6 | class Fallback < Plugin 7 | # 8 | # Represents the `` XML element. 9 | # 10 | class TLSFallbackSCSV 11 | 12 | include IsSupported 13 | 14 | # 15 | # Initializes the {TLSFallbackSCSV} object. 16 | # 17 | # @param [Nokogiri::XML::Element] node 18 | # The `` XML element. 19 | # 20 | def initialize(node) 21 | @node = node 22 | end 23 | 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/sslyze/xml/heartbleed.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | require 'sslyze/xml/heartbleed/openssl_heartbleed' 4 | 5 | module SSLyze 6 | class XML 7 | # 8 | # Represents the `` XML element. 9 | # 10 | # @since 1.0.0 11 | # 12 | class Heartbleed < Plugin 13 | 14 | # 15 | # Parses the `` XML element. 16 | # 17 | # @return [OpenSSLHeartbleed] 18 | # 19 | def openssl_heartbleed 20 | @openssl_heartbleed ||= if (element = @node.at_xpath('openSslHeartbleed')) 21 | OpenSSLHeartbleed.new(element) 22 | end 23 | end 24 | 25 | alias openssl openssl_heartbleed 26 | 27 | # 28 | # @see #has_openssl_heartbleed? 29 | # 30 | def is_vulnerable? 31 | openssl_heartbleed && openssl_heartbleed.is_vulnerable? 32 | end 33 | 34 | alias vulnerable? is_vulnerable? 35 | 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/sslyze/xml/heartbleed/openssl_heartbleed.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/attributes/is_vulnerable' 3 | 4 | module SSLyze 5 | class XML 6 | class Heartbleed < Plugin 7 | # 8 | # Represents the `` XML element. 9 | # 10 | # @since 1.0.0 11 | # 12 | class OpenSSLHeartbleed 13 | 14 | include Attributes::IsVulnerable 15 | 16 | # 17 | # Initializes the {OpenSSLHeartbleed} object. 18 | # 19 | # @param [Nokogiri::XML::Element] node 20 | # The `` XML element. 21 | # 22 | def initialize(node) 23 | @node = node 24 | end 25 | 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/sslyze/xml/http_headers.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/http_headers/http_strict_transport_security' 3 | require 'sslyze/xml/http_headers/http_public_key_pinning' 4 | 5 | module SSLyze 6 | class XML 7 | # 8 | # Represents the `` XML element. 9 | # 10 | # @since 1.0.0 11 | # 12 | class HTTPHeaders < Plugin 13 | 14 | # 15 | # HTTP Strict-Transport-Security header information. 16 | # 17 | # @return [HTTPStrictTransportSecurity, nil] 18 | # 19 | def http_strict_transport_security 20 | @http_strict_transport_security ||= if (element = @node.at_xpath('httpStrictTransportSecurity')) 21 | HTTPStrictTransportSecurity.new(element) 22 | end 23 | end 24 | 25 | alias strict_transport_security http_strict_transport_security 26 | 27 | # 28 | # HTTP Public-Key-Pinning header information. 29 | # 30 | # @return [HTTPPublicKeyPinning, nil] 31 | # 32 | def http_public_key_pinning 33 | @http_public_key_pinning ||= if (element = @node.at_xpath('httpPublicKeyPinning')) 34 | HTTPPublicKeyPinning.new(element) 35 | end 36 | end 37 | 38 | alias public_key_pinning http_public_key_pinning 39 | 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/sslyze/xml/http_headers/http_public_key_pinning.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | require 'sslyze/xml/attributes/is_supported' 4 | require 'sslyze/xml/attributes/exception' 5 | 6 | module SSLyze 7 | class XML 8 | class HTTPHeaders < Plugin 9 | # 10 | # Represents the `` XML element. 11 | # 12 | # @since 1.0.0 13 | # 14 | class HTTPPublicKeyPinning 15 | 16 | include Types 17 | include Attributes::IsSupported 18 | include Attributes::Exception 19 | 20 | # 21 | # Initializes the {HTTPPublicKeyPinning} element. 22 | # 23 | def initialize(node) 24 | @node = node 25 | end 26 | 27 | # 28 | # Parses each `pinSha256` XML element. 29 | # 30 | # @yield [sha256] 31 | # Yields each SHA256 checksum. 32 | # 33 | # @yieldparam [String] sha256 34 | # An individual pinned SHA256 checksum. 35 | # 36 | # @return [Enumerator] 37 | # 38 | def each_pin_sha256 39 | return enum_for(__method__) unless block_given? 40 | 41 | @node.xpath('pinSha256').each do |element| 42 | yield element.inner_text 43 | end 44 | end 45 | 46 | alias each_sha256 each_pin_sha256 47 | 48 | # 49 | # @return [Array] 50 | # 51 | # @see #each_pin_sha256 52 | # 53 | def pin_sha256s 54 | each_pin_sha256.to_a 55 | end 56 | 57 | alias sha256s pin_sha256s 58 | 59 | # 60 | # Parses the `includeSubDomains` XML attribute. 61 | # 62 | # @return [Boolean] 63 | # 64 | def include_sub_domains? 65 | Boolean[@node['includeSubDomains']] 66 | end 67 | 68 | # 69 | # Parses the `maxAge` attribute. 70 | # 71 | # @return [Integer, nil] 72 | # 73 | def max_age 74 | @max_age ||= if (value = @node['maxAge']) 75 | value.to_i 76 | end 77 | end 78 | 79 | # 80 | # Parses the `reportOnly` XML attribute. 81 | # 82 | # @return [Boolean] 83 | # 84 | def report_only 85 | Boolean[@node['reportOnly']] 86 | end 87 | 88 | # 89 | # Parses the `reportUri` XML attribute. 90 | # 91 | # @return [String, nil] 92 | # 93 | def report_uri 94 | @report_uri ||= case (value = @node['reportUri']) 95 | when nil, 'None' then nil 96 | else value 97 | end 98 | end 99 | 100 | # 101 | # Parses the `isValidPinConfigured` XML attribute. 102 | # 103 | # @return [Boolean] 104 | # 105 | def is_valid_pin_configured? 106 | Boolean[@node['isValidPinConfigured']] 107 | end 108 | 109 | # 110 | # Parses the `isBackupPinConfigured` XML attribute. 111 | # 112 | # @return [Boolean] 113 | # 114 | def is_backup_pin_configured? 115 | Boolean[@node['isBackupPinConfigured']] 116 | end 117 | 118 | end 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/sslyze/xml/http_headers/http_strict_transport_security.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | require 'sslyze/xml/attributes/is_supported' 4 | require 'sslyze/xml/attributes/exception' 5 | 6 | module SSLyze 7 | class XML 8 | class HTTPHeaders < Plugin 9 | # 10 | # Represents the `` XML element. 11 | # 12 | # @since 1.0.0 13 | # 14 | class HTTPStrictTransportSecurity 15 | 16 | include Types 17 | include Attributes::IsSupported 18 | include Attributes::Exception 19 | 20 | # 21 | # Initializes the {HTTPStrictTransportSecurity} object. 22 | # 23 | def initialize(node) 24 | @node = node 25 | end 26 | 27 | # 28 | # Parses the `includeSubDomains` XML attribute. 29 | # 30 | # @return [Boolean] 31 | # 32 | def include_sub_domains? 33 | Boolean[@node['includeSubDomains']] 34 | end 35 | 36 | # 37 | # Parses the `maxAge` XML attribute. 38 | # 39 | # @return [Integer, nil] 40 | # 41 | def max_age 42 | @max_age ||= if (value = @node['maxAge']) 43 | value.to_i 44 | end 45 | end 46 | 47 | # 48 | # Parses the `preload` XML attribute. 49 | # 50 | # @return [Boolean] 51 | # 52 | def preload? 53 | Boolean[@node['preload']] 54 | end 55 | 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/sslyze/xml/invalid_target.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/attributes/error' 2 | 3 | module SSLyze 4 | class XML 5 | # 6 | # Represents the `` XML element. 7 | # 8 | class InvalidTarget 9 | 10 | include Attributes::Error 11 | 12 | # 13 | # Initializes the invalid target. 14 | # 15 | # @param [Nokogiri::XML::Node] node 16 | # The `` XML element. 17 | # 18 | def initialize(node) 19 | @node = node 20 | end 21 | 22 | # 23 | # The target name. 24 | # 25 | # @return [String] 26 | # 27 | # @since 1.1.0 28 | # 29 | def target 30 | @target ||= @node.inner_text 31 | end 32 | 33 | # 34 | # The host component of the target. 35 | # 36 | # @return [String] 37 | # 38 | def host 39 | @host ||= target.split(':',2).first 40 | end 41 | 42 | # 43 | # The port component of the target. 44 | # 45 | # @return [Integer] 46 | # 47 | # @since 1.1.0 48 | # 49 | def port 50 | @port ||= target.split(':',2).last.to_i 51 | end 52 | 53 | 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/sslyze/xml/openssl_ccs.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | 3 | module SSLyze 4 | class XML 5 | # 6 | # Represents the `` XML element. 7 | # 8 | # @since 1.0.0 9 | # 10 | class OpenSSLCCS < Plugin 11 | 12 | # 13 | # @return [OpenSSLCCSInjection] 14 | # 15 | def openssl_ccs_injection 16 | @openssl_ccs_injection ||= OpenSSLCCSInjection.new( 17 | @node.at_xpath('openSslCcsInjection') 18 | ) 19 | end 20 | 21 | alias injection openssl_ccs_injection 22 | 23 | # 24 | # @return [Boolean] 25 | # 26 | def is_vulnerable? 27 | openssl_ccs_injection.is_vulnerable? 28 | end 29 | 30 | alias vulnerable? is_vulnerable? 31 | 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/sslyze/xml/openssl_ccs/openssl_ccs_injection.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/attributes/is_vulnerable' 3 | 4 | module SSLyze 5 | class XML 6 | class OpenSSLCCS < Plugin 7 | # 8 | # Represents the `` XML element. 9 | # 10 | # @since 1.0.0 11 | # 12 | class OpenSSLCCSInjection 13 | 14 | include Attributes::IsVulnerable 15 | 16 | # 17 | # Initializes the {OpenSSLCCSInjection} object. 18 | # 19 | def initialize(node) 20 | @node = node 21 | end 22 | 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/sslyze/xml/plugin.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/attributes/title' 2 | require 'sslyze/xml/attributes/exception' 3 | 4 | module SSLyze 5 | class XML 6 | # 7 | # Common base class for all plugin XML elements. 8 | # 9 | # @since 1.0.0 10 | # 11 | class Plugin 12 | 13 | include Attributes::Title 14 | include Attributes::Exception 15 | 16 | # 17 | # Initializes the plugin. 18 | # 19 | # @param [Nokogiri::XML::Element] node 20 | # 21 | def initialize(node) 22 | @node = node 23 | end 24 | 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/sslyze/xml/protocol.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | require 'sslyze/xml/protocol/cipher_suite' 4 | 5 | module SSLyze 6 | class XML 7 | # 8 | # Represents the ``, ``, ``, ``, `` 9 | # XML elements. 10 | # 11 | class Protocol < Plugin 12 | 13 | include Types 14 | 15 | # SSL protocol name. 16 | # 17 | # @return [Symbol] 18 | attr_reader :name 19 | 20 | # 21 | # Initializes the protocol. 22 | # 23 | # @param [Nokogiri::XML::Node] node 24 | # The XML element. 25 | # 26 | def initialize(node) 27 | @node = node 28 | @name = @node.name.to_sym 29 | end 30 | 31 | # 32 | # Determines whether the protocol is supported. 33 | # 34 | # @return [Boolean] 35 | # Specifies whether any cipher suite was accepted. 36 | # 37 | # @since 1.0.0 38 | # 39 | def is_protocol_supported? 40 | Boolean[@node['isProtocolSupported']] 41 | end 42 | 43 | alias is_supported? is_protocol_supported? 44 | alias supported? is_protocol_supported? 45 | 46 | # 47 | # Enumerates over every rejected cipher suite. 48 | # 49 | # @yield [cipher_suite] 50 | # 51 | # @yieldparam [CipherSuite] cipher_suite 52 | # 53 | # @return [Enumerator] 54 | # 55 | def each_rejected_cipher_suite 56 | return enum_for(__method__) unless block_given? 57 | 58 | @node.xpath('rejectedCipherSuites/cipherSuite').each do |cipher_suite| 59 | yield CipherSuite.new(cipher_suite) 60 | end 61 | end 62 | 63 | # 64 | # The rejected cipher suites. 65 | # 66 | # @return [Array] 67 | # 68 | def rejected_cipher_suites 69 | each_rejected_cipher_suite.to_a 70 | end 71 | 72 | # 73 | # Enumerates over every accepted cipher suite. 74 | # 75 | # @yield [cipher_suite] 76 | # 77 | # @yieldparam [CipherSuite] cipher_suite 78 | # 79 | # @return [Enumerator] 80 | # 81 | def each_accepted_cipher_suite 82 | return enum_for(__method__) unless block_given? 83 | 84 | @node.xpath('acceptedCipherSuites/cipherSuite').each do |cipher_suite| 85 | yield CipherSuite.new(cipher_suite) 86 | end 87 | end 88 | 89 | # 90 | # The accepted cipher suites. 91 | # 92 | # @return [Array] 93 | # 94 | def accepted_cipher_suites 95 | each_accepted_cipher_suite.to_a 96 | end 97 | 98 | # 99 | # The preferred cipher suite. 100 | # 101 | # @return [CipherSuite, nil] 102 | # 103 | # @since 1.0.0 104 | # 105 | def preferred_cipher_suite 106 | @preferred_cipher_suite ||= if (element = @node.at_xpath('preferredCipherSuite/cipherSuite')) 107 | CipherSuite.new(element) 108 | end 109 | end 110 | 111 | # 112 | # Enumerates over every errored cipher suite. 113 | # 114 | # @yield [cipher_suite] 115 | # 116 | # @yieldparam [CipherSuite] cipher_suite 117 | # 118 | # @return [Enumerator] 119 | # 120 | # @since 1.0.0 121 | # 122 | def each_error 123 | return enum_for(__method__) unless block_given? 124 | 125 | @node.xpath('errors/cipherSuite').each do |cipher_suite| 126 | yield CipherSuite.new(cipher_suite) 127 | end 128 | end 129 | 130 | # 131 | # The errored cipher suites. 132 | # 133 | # @return [Array] 134 | # 135 | # @since 1.0.0 136 | # 137 | def errors 138 | each_error.to_a 139 | end 140 | 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /lib/sslyze/xml/protocol/cipher_suite.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | require 'sslyze/xml/protocol/cipher_suite/key_exchange' 4 | require 'sslyze/cipher_suites' 5 | 6 | module SSLyze 7 | class XML 8 | class Protocol < Plugin 9 | # 10 | # Represents the `` XML element. 11 | # 12 | class CipherSuite 13 | 14 | include Types 15 | 16 | # 17 | # Initializes the cipher suite. 18 | # 19 | # @param [Nokogiri::XML::Node] node 20 | # The `` XML element. 21 | # 22 | def initialize(node) 23 | @node = node 24 | end 25 | 26 | # 27 | # The cipher suite name. 28 | # 29 | # @return [String] 30 | # 31 | def name 32 | @name ||= @node['name'] 33 | end 34 | 35 | alias rfc_name name 36 | 37 | # 38 | # Maps the RFC cipher name to it's OpenSSL name. 39 | # 40 | # @return [String, nil] 41 | # 42 | # @since 1.0.0 43 | # 44 | def openssl_name 45 | CipherSuites::OPENSSL_NAMES[rfc_name] 46 | end 47 | 48 | # 49 | # The connection status when the cipher suite was used. 50 | # 51 | # @return [String] 52 | # 53 | def connection_status 54 | @connection_status ||= @node['connectionStatus'] 55 | end 56 | 57 | # 58 | # @return [Boolean] 59 | # 60 | def anonymous? 61 | Boolean[@node['anonymous']] 62 | end 63 | 64 | # 65 | # The key size required by the cipher suite. 66 | # 67 | # @return [Integer, nil] 68 | # 69 | # @since 1.0.0 70 | # 71 | def key_size 72 | @key_size ||= if (value = @node['keySize']) 73 | value.to_i 74 | end 75 | end 76 | 77 | # 78 | # Key exchange information. 79 | # 80 | # @return [KeyExchange, nil] 81 | # 82 | def key_exchange 83 | @key_exchange ||= if (element = @node.at_xpath('keyExchange')) 84 | KeyExchange.new(element) 85 | end 86 | end 87 | 88 | alias to_s name 89 | 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/sslyze/xml/protocol/cipher_suite/key_exchange.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | 3 | module SSLyze 4 | class XML 5 | class Protocol < Plugin 6 | class CipherSuite 7 | # 8 | # Key exchange information. 9 | # 10 | class KeyExchange 11 | 12 | # 13 | # Initializes the key exchange information. 14 | # 15 | # @param [Nokogiri::XML::Node] node 16 | # The `` information. 17 | # 18 | def initialize(node) 19 | @node = node 20 | end 21 | 22 | # 23 | # @return [String, nil] 24 | # 25 | def a 26 | @a ||= @node['A'] 27 | end 28 | 29 | # 30 | # @return [String, nil] 31 | # 32 | def b 33 | @b ||= @node['B'] 34 | end 35 | 36 | # 37 | # @return [Integer, nil] 38 | # 39 | def cofactor 40 | @cofactor ||= if (value = @node['Cofactor']) 41 | value.to_i 42 | end 43 | end 44 | 45 | # 46 | # @return [String, nil] 47 | # 48 | def field_type 49 | @field_type ||= @node['Field_Type'] 50 | end 51 | 52 | # 53 | # @return [String] 54 | # 55 | def generator 56 | @generator ||= @node['Generator'] 57 | end 58 | 59 | # 60 | # @return [Symbol, nil] 61 | # 62 | def generator_type 63 | @generator_type ||= if (value = @node['GeneratorType']) 64 | value.to_sym 65 | end 66 | end 67 | 68 | # 69 | # @return [Integer] 70 | # 71 | def group_size 72 | @group_size ||= @node['GroupSize'].to_i 73 | end 74 | 75 | # 76 | # @return [String, nil] 77 | # 78 | # @since 1.0.0 79 | # 80 | def order 81 | @order ||= @node['Order'] 82 | end 83 | 84 | # 85 | # @return [String, nil] 86 | # 87 | def prime 88 | @prime ||= @node['Prime'] 89 | end 90 | 91 | # 92 | # @return [String, nil] 93 | # 94 | def seed 95 | @seed ||= @node['Seed'] 96 | end 97 | 98 | # 99 | # @return [Symbol] 100 | # 101 | def type 102 | @type ||= @node['Type'].to_sym 103 | end 104 | 105 | # 106 | # Determines if DH key exchange was used. 107 | # 108 | # @return [Boolean] 109 | # 110 | def dh? 111 | type == :DH 112 | end 113 | 114 | # 115 | # Determines if ECDHE key exchange was used. 116 | # 117 | # @return [Boolean] 118 | # 119 | def ecdhe? 120 | type == :ECDHE 121 | end 122 | 123 | end 124 | end 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /lib/sslyze/xml/reneg.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/reneg/session_renegotiation' 3 | 4 | module SSLyze 5 | class XML 6 | # 7 | # Represents the `` element. 8 | # 9 | # @since 1.0.0 10 | # 11 | class Reneg < Plugin 12 | 13 | # 14 | # Session Renegotiation information. 15 | # 16 | # @return [SessionRenegotiation, nil] 17 | # 18 | def session_renegotiation 19 | @session_renegotiation ||= if (element = @node.at_xpath('sessionRenegotiation')) 20 | SessionRenegotiation.new(element) 21 | end 22 | end 23 | 24 | alias session session_renegotiation 25 | 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/sslyze/xml/reneg/session_renegotiation.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | 4 | module SSLyze 5 | class XML 6 | class Reneg < Plugin 7 | # 8 | # Represents the `` XML element. 9 | # 10 | # @since 1.0.0 11 | # 12 | class SessionRenegotiation 13 | 14 | include Types 15 | 16 | # 17 | # Initializes the {SessionRenegotiation} object. 18 | # 19 | # @param [Nokogiri::XML::Element] node 20 | # The `` XML element. 21 | # 22 | def initialize(node) 23 | @node = node 24 | end 25 | 26 | # 27 | # Returns the `canBeClientInitiated` attribute. 28 | # 29 | # @return [Boolean] 30 | # 31 | def can_be_client_initiated? 32 | Boolean[@node['canBeClientInitiated']] 33 | end 34 | 35 | alias client_initiated? can_be_client_initiated? 36 | 37 | # 38 | # Returns the `isSecure` attribute. 39 | # 40 | # @return [Boolean] 41 | # 42 | def is_secure? 43 | Boolean[@node['isSecure']] 44 | end 45 | 46 | alias secure? is_secure? 47 | 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/sslyze/xml/resum.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/resum/session_resumption_with_session_ids' 3 | require 'sslyze/xml/resum/session_resumption_with_tls_tickets' 4 | 5 | module SSLyze 6 | class XML 7 | # 8 | # Represents the `` XML element. 9 | # 10 | # @since 1.0.0 11 | # 12 | class Resum < Plugin 13 | 14 | # 15 | # Parses the `` XML element. 16 | # 17 | # @return [SessionResumptionWithSessionIDs, nil] 18 | # 19 | def session_resumption_with_session_ids 20 | @session_resumption_with_session_ids ||= if (element = @node.at_xpath('sessionResumptionWithSessionIDs')) 21 | SessionResumptionWithSessionIDs.new(element) 22 | end 23 | end 24 | 25 | alias with_session_ids session_resumption_with_session_ids 26 | 27 | # 28 | # Parses the `` XML element. 29 | # 30 | # @return [SessionResumptionWithTLSTickets, nil] 31 | # 32 | def session_resumption_with_tls_tickets 33 | @session_resumption_with_tls_tickets ||= if (element = @node.at_xpath('sessionResumptionWithTLSTickets')) 34 | SessionResumptionWithTLSTickets.new(element) 35 | end 36 | end 37 | 38 | alias with_tls_tickets session_resumption_with_tls_tickets 39 | 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/sslyze/xml/resum/session_resumption_with_session_ids.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | require 'sslyze/xml/attributes/is_supported' 4 | 5 | module SSLyze 6 | class XML 7 | class Resum < Plugin 8 | # 9 | # Represents the `` XML element. 10 | # 11 | # @since 1.0.0 12 | # 13 | class SessionResumptionWithSessionIDs 14 | 15 | include Types 16 | include Attributes::IsSupported 17 | 18 | # 19 | # Initializes the {SessionResumptionWithSessionIDs} object. 20 | # 21 | # @param [Nokogiri::XML::Element] node 22 | # The `` XML element. 23 | # 24 | def initialize(node) 25 | @node = node 26 | end 27 | 28 | # 29 | # Number of failed attempts. 30 | # 31 | # @return [Integer] 32 | # 33 | def failed_attempts 34 | @failed_attempts ||= @node['failedAttempts'].to_i 35 | end 36 | 37 | # 38 | # Number of successful attempts. 39 | # 40 | # @return [Integer] 41 | # 42 | def successful_attempts 43 | @successful_attempts ||= @node['successfulAttempts'].to_i 44 | end 45 | 46 | # 47 | # Number of total attempts. 48 | # 49 | # @return [Integer] 50 | # 51 | def total_attempts 52 | @total_attempts ||= @node['totalAttempts'].to_i 53 | end 54 | 55 | # 56 | # The number of errors that occurred. 57 | # 58 | # @return [Integer] 59 | # 60 | def error_count 61 | @error_count ||= @node['errors'].to_i 62 | end 63 | 64 | # 65 | # Enumerates over each error message. 66 | # 67 | # @yield [error] 68 | # 69 | # @yieldparam [String] error 70 | # 71 | # @return [Enumerator] 72 | # An enumerator will be returned if no block was given. 73 | # 74 | def each_error 75 | return enum_for(__method__) unless block_given? 76 | 77 | @node.xpath('error').each do |error| 78 | yield error.inner_text 79 | end 80 | end 81 | 82 | # 83 | # All error messages. 84 | # 85 | # @return [Array] 86 | # 87 | def errors 88 | each_error.to_a 89 | end 90 | 91 | end 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/sslyze/xml/resum/session_resumption_with_tls_tickets.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/types' 3 | require 'sslyze/xml/attributes/is_supported' 4 | require 'sslyze/xml/attributes/error' 5 | 6 | module SSLyze 7 | class XML 8 | class Resum < Plugin 9 | # 10 | # Represents the `` XML element. 11 | # 12 | # @since 1.0.0 13 | # 14 | class SessionResumptionWithTLSTickets 15 | 16 | include Types 17 | include Attributes::IsSupported 18 | include Attributes::Error 19 | 20 | # 21 | # Initializes the {SessionResumptionWithTLSTickets} object. 22 | # 23 | # @param [Nokogiri::XML::Element] node 24 | # The `` XML element. 25 | # 26 | def initialize(node) 27 | @node = node 28 | end 29 | 30 | # 31 | # Returns the `error` attribute. 32 | # 33 | # @return [String, nil] 34 | # 35 | def error 36 | @error ||= @node['error'] 37 | end 38 | 39 | # 40 | # Determines if there was an error. 41 | # 42 | # @return [Boolean] 43 | # 44 | def error? 45 | !error.nil? 46 | end 47 | 48 | # 49 | # Returns the descriptive reason. 50 | # 51 | # @return [String, nil] 52 | # 53 | def reason 54 | @reason ||= @node['reason'] 55 | end 56 | 57 | # 58 | # Converts the element to a String. 59 | # 60 | # @return [String] 61 | # 62 | def to_s 63 | reason || '' 64 | end 65 | 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/sslyze/xml/resum_rate.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/plugin' 2 | require 'sslyze/xml/resum/session_resumption_with_session_ids' 3 | 4 | module SSLyze 5 | class XML 6 | # 7 | # Represents the `` XML element. 8 | # 9 | # @since 1.0.0 10 | # 11 | class ResumRate < Plugin 12 | 13 | SessionResumptionWithSessionIDs = Resum::SessionResumptionWithSessionIDs 14 | 15 | # 16 | # Parses the `` XML element. 17 | # 18 | # @return [SessionResumptionWithSessionIDs, nil] 19 | # 20 | def session_resumption_with_session_ids 21 | @session_resumption_with_session_ids ||= if (element = @node.at_xpath('sessionResumptionWithSessionIDs')) 22 | SessionResumptionWithSessionIDs.new(element) 23 | end 24 | end 25 | 26 | alias with_session_ids session_resumption_with_session_ids 27 | 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/sslyze/xml/target.rb: -------------------------------------------------------------------------------- 1 | require 'sslyze/xml/types' 2 | require 'sslyze/xml/certinfo' 3 | require 'sslyze/xml/compression' 4 | require 'sslyze/xml/heartbleed' 5 | require 'sslyze/xml/http_headers' 6 | require 'sslyze/xml/reneg' 7 | require 'sslyze/xml/resum' 8 | require 'sslyze/xml/resum_rate' 9 | require 'sslyze/xml/protocol' 10 | require 'sslyze/xml/fallback' 11 | require 'sslyze/xml/openssl_ccs' 12 | 13 | require 'ipaddr' 14 | 15 | module SSLyze 16 | class XML 17 | # 18 | # Represents the `` XML element. 19 | # 20 | class Target 21 | 22 | include Types 23 | 24 | # 25 | # Initializes the target. 26 | # 27 | # @param [Nokogiri::XML::Node] node 28 | # The `` XML element. 29 | # 30 | def initialize(node) 31 | @node = node 32 | end 33 | 34 | # 35 | # The host name of the target. 36 | # 37 | # @return [String] 38 | # 39 | def host 40 | @host ||= @node['host'] 41 | end 42 | 43 | # 44 | # The IP address of the target. 45 | # 46 | # @return [String] 47 | # 48 | def ip 49 | @ip ||= @node['ip'] 50 | end 51 | 52 | # 53 | # The IP address of the target. 54 | # 55 | # @return [IPAddr] 56 | # 57 | # @since 1.0.0 58 | # 59 | def ipaddr 60 | IPAddr.new(ip) 61 | end 62 | 63 | # 64 | # The port number that was scanned. 65 | # 66 | # @return [Integer] 67 | # 68 | def port 69 | @port ||= @node['port'].to_i 70 | end 71 | 72 | # 73 | # Which compression algorithms are supported. 74 | # 75 | # @return [Compression, nil] 76 | # 77 | def compression 78 | @compression ||= if (element = @node.at_xpath('compression')) 79 | Compression.new(element) 80 | end 81 | end 82 | 83 | # 84 | # Certificate information. 85 | # 86 | # @return [Certinfo, nil] 87 | # 88 | # @since 1.0.0 89 | # 90 | def certinfo 91 | @cert_info ||= if (element = @node.at_xpath('certinfo')) 92 | Certinfo.new(element) 93 | end 94 | end 95 | 96 | alias cert_info certinfo 97 | 98 | # 99 | # @return [Heartbleed, nil] 100 | # 101 | # @since 1.0.0 102 | # 103 | def heartbleed 104 | @heartbleed ||= if (element = @node.at_xpath('heartbleed')) 105 | Heartbleed.new(element) 106 | end 107 | end 108 | 109 | # 110 | # @return [HTTPHeaders, nil] 111 | # 112 | # @since 1.0.0 113 | # 114 | def http_headers 115 | @http_headers ||= if (element = @node.at_xpath('http_headers')) 116 | HTTPHeaders.new(element) 117 | end 118 | end 119 | 120 | # 121 | # Specifies whether the service supports Session Renegotiation. 122 | # 123 | # @return [Reneg, nil] 124 | # 125 | # @since 1.0.0 126 | # 127 | def reneg 128 | @reneg ||= if (element = @node.at_xpath('reneg')) 129 | Reneg.new(element) 130 | end 131 | end 132 | 133 | alias session_renegotiation reneg 134 | 135 | # 136 | # @return [Resum, nil] 137 | # 138 | # @since 1.0.0 139 | # 140 | def resum 141 | @resum ||= if (element = @node.at_xpath('resum')) 142 | Resum.new(element) 143 | end 144 | end 145 | 146 | alias session_resumption resum 147 | 148 | # 149 | # @return [Resum, nil] 150 | # 151 | # @since 1.0.0 152 | # 153 | def resum_rate 154 | @resum ||= if (element = @node.at_xpath('resum_rate')) 155 | ResumRate.new(element) 156 | end 157 | end 158 | 159 | alias session_resumption resum_rate 160 | 161 | # 162 | # SSLv2 protocol information. 163 | # 164 | # @return [Protocol, nil] 165 | # 166 | def sslv2 167 | @sslv2 ||= if (element = @node.at_xpath('sslv2')) 168 | Protocol.new(element) 169 | end 170 | end 171 | 172 | alias ssl_v2 sslv2 173 | 174 | # 175 | # SSLv3 protocol information. 176 | # 177 | # @return [Protocol, nil] 178 | # 179 | def sslv3 180 | @sslv3 ||= if (element = @node.at_xpath('sslv3')) 181 | Protocol.new(element) 182 | end 183 | end 184 | 185 | alias ssl_v3 sslv3 186 | 187 | # 188 | # TLSv1 protocol information. 189 | # 190 | # @return [Protocol, nil] 191 | # 192 | def tlsv1 193 | @tlsv1 ||= if (element = @node.at_xpath('tlsv1')) 194 | Protocol.new(element) 195 | end 196 | end 197 | 198 | alias tls_v1 tlsv1 199 | 200 | # 201 | # TLSv1.1 protocol information. 202 | # 203 | # @return [Protocol, nil] 204 | # 205 | def tlsv1_1 206 | @tlsv1_1 ||= if (element = @node.at_xpath('tlsv1_1')) 207 | Protocol.new(element) 208 | end 209 | end 210 | 211 | alias tls_v1_1 tlsv1_1 212 | 213 | # 214 | # TLSv1.2 protocol information. 215 | # 216 | # @return [Protocol, nil] 217 | # 218 | def tlsv1_2 219 | @tlsv1_2 ||= if (element = @node.at_xpath('tlsv1_2')) 220 | Protocol.new(element) 221 | end 222 | end 223 | 224 | alias tls_v1_2 tlsv1_2 225 | 226 | # 227 | # Iterates over every SSL protocol. 228 | # 229 | # @yield [protocol] 230 | # The given block will be passed each SSL protocol. 231 | # 232 | # @yieldparam [Protocol] protocol 233 | # A SSL protocol. 234 | # 235 | # @return [Enumerator] 236 | # If a no block was given, an Enumerator will be returned. 237 | # 238 | # @see #sslv2 239 | # @see #sslv3 240 | # 241 | def each_ssl_protocol 242 | return enum_for(__method__) unless block_given? 243 | 244 | yield sslv2 if sslv2 245 | yield sslv3 if sslv3 246 | end 247 | 248 | # 249 | # All supported SSL protocols. 250 | # 251 | # @return [Array] 252 | # 253 | def ssl_protocols 254 | each_ssl_protocol.to_a 255 | end 256 | 257 | # 258 | # Iterates over every TLS protocol. 259 | # 260 | # @yield [protocol] 261 | # The given block will be passed each TLS protocol. 262 | # 263 | # @yieldparam [Protocol] protocol 264 | # A TLS protocol. 265 | # 266 | # @return [Enumerator] 267 | # If a no block was given, an Enumerator will be returned. 268 | # 269 | # @see #tlsv1 270 | # @see #tlsv1_1 271 | # @see #tlsv1_2 272 | # 273 | def each_tls_protocol 274 | return enum_for(__method__) unless block_given? 275 | 276 | yield tlsv1 if tlsv1 277 | yield tlsv1_1 if tlsv1_1 278 | yield tlsv1_2 if tlsv1_2 279 | end 280 | 281 | # 282 | # All supported TLS protocols. 283 | # 284 | # @return [Array] 285 | # 286 | def tls_protocols 287 | each_tls_protocol.to_a 288 | end 289 | 290 | # 291 | # Iterates over every SSL/TLS protocol. 292 | # 293 | # @yield [protocol] 294 | # The given block will be passed each SSL/TLS protocol. 295 | # 296 | # @yieldparam [Protocol] protocol 297 | # A SSL/TLS protocol. 298 | # 299 | # @return [Enumerator] 300 | # If a no block was given, an Enumerator will be returned. 301 | # 302 | # @see #sslv2 303 | # @see #sslv3 304 | # @see #tlsv1 305 | # @see #tlsv1_1 306 | # @see #tlsv1_2 307 | # 308 | def each_protocol(&block) 309 | return enum_for(__method__) unless block 310 | 311 | each_ssl_protocol(&block) 312 | each_tls_protocol(&block) 313 | end 314 | 315 | # 316 | # All supported SSL/TLS protocols. 317 | # 318 | # @return [Array] 319 | # 320 | def protocols 321 | each_protocol.to_a 322 | end 323 | 324 | # 325 | # @return [Fallback, nil] 326 | # 327 | # @since 1.0.0 328 | # 329 | def fallback 330 | @fallback ||= if (element = @node.at_xpath('fallback')) 331 | Fallback.new(element) 332 | end 333 | end 334 | 335 | # 336 | # @return [OpenSSLCCS, nil] 337 | # 338 | # @since 1.0.0 339 | # 340 | def openssl_ccs 341 | @openssl_ccs ||= if (element = @node.at_xpath('openssl_ccs')) 342 | OpenSSLCCS.new(element) 343 | end 344 | end 345 | 346 | # 347 | # Convert the target to a String. 348 | # 349 | # @return [String] 350 | # The host and port. 351 | # 352 | def to_s 353 | "#{host}:#{port}" 354 | end 355 | 356 | # 357 | # Compares the other target to this target. 358 | # 359 | # @param [Target] other 360 | # The other target. 361 | # 362 | # @return [Boolean] 363 | # Whether the other target has the same {#host} and {#port}. 364 | # 365 | def ==(other) 366 | other.kind_of?(self.class) && other.host == host && other.port == port 367 | end 368 | 369 | end 370 | end 371 | end 372 | -------------------------------------------------------------------------------- /lib/sslyze/xml/types.rb: -------------------------------------------------------------------------------- 1 | module SSLyze 2 | class XML 3 | module Types 4 | # Maps `"True"` and `"False"` to boolean values. 5 | Boolean = { 6 | 'True' => true, 7 | 'False' => false 8 | } 9 | 10 | # Maps `"None"` to `nil` 11 | None = proc { |value| 12 | case value 13 | when 'None' then nil 14 | else value 15 | end 16 | } 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /ruby-sslyze.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | require File.expand_path('../lib/sslyze/version', __FILE__) 4 | 5 | Gem::Specification.new do |gem| 6 | gem.name = "ruby-sslyze" 7 | gem.version = SSLyze::VERSION 8 | gem.summary = %q{Ruby interface to sslyze} 9 | gem.description = %q{A ruby interface to the sslyze python utility} 10 | gem.license = "MIT" 11 | gem.authors = ["Hal Brodigan"] 12 | gem.email = "postmodern.mod3@gmail.com" 13 | gem.homepage = "https://github.com/trailofbits/ruby-sslyze#readme" 14 | 15 | gem.files = `git ls-files`.split($/) 16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.require_paths = ['lib'] 19 | 20 | gem.requirements << 'sslyze >= 1.4.0' 21 | 22 | gem.add_dependency 'rprogram', '~> 0.3' 23 | gem.add_dependency 'nokogiri', '~> 1.8' 24 | 25 | gem.add_development_dependency 'bundler', '~> 2.0' 26 | end 27 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start 3 | 4 | require 'rspec' 5 | require 'sslyze/version' 6 | 7 | include SSLyze 8 | -------------------------------------------------------------------------------- /spec/sslyze_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'sslyze' 3 | 4 | describe SSLyze do 5 | it "should have a VERSION constant" do 6 | expect(subject.const_get('VERSION')).to_not be_empty 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/x509/domain_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'sslyze/x509/domain' 3 | 4 | describe SSLyze::X509::Domain do 5 | let(:domain_name) { 'example.com' } 6 | let(:wildcard_name) { "*.#{domain_name}" } 7 | 8 | subject { described_class.new(domain_name) } 9 | 10 | describe "#initialize" do 11 | context "when given a fully qualified domain name" do 12 | subject { described_class.new(domain_name) } 13 | 14 | it "should set #name" do 15 | expect(subject.name).to be == domain_name 16 | end 17 | 18 | it "should not set #suffix" do 19 | expect(subject.suffix).to be nil 20 | end 21 | 22 | it "should not set #domain to #name" do 23 | expect(subject.domain).to be == domain_name 24 | end 25 | end 26 | 27 | context "when given a wildcard domain name" do 28 | subject { described_class.new(wildcard_name) } 29 | 30 | it "should set #name" do 31 | expect(subject.name).to be == wildcard_name 32 | end 33 | 34 | it "should set #suffix" do 35 | expect(subject.suffix).to be == ".#{domain_name}" 36 | end 37 | 38 | it "should set #domain" do 39 | expect(subject.domain).to be == domain_name 40 | end 41 | end 42 | end 43 | 44 | describe "#==" do 45 | context "when given another #{described_class}" do 46 | subject { described_class.new(domain_name) } 47 | 48 | context "and it matches the domain name" do 49 | let(:other) { described_class.new(domain_name) } 50 | 51 | it { expect(subject == other).to be true } 52 | end 53 | 54 | context "but it doesn't match" do 55 | let(:other) { described_class.new('foo.com') } 56 | 57 | it { expect(subject == other).to be false } 58 | end 59 | end 60 | 61 | context "when given a non-#{described_class}" do 62 | let(:other) { Object.new } 63 | 64 | subject { described_class.new(domain_name) } 65 | 66 | it "should return false" do 67 | expect(subject == other).to be false 68 | end 69 | end 70 | end 71 | 72 | describe "#include?" do 73 | context "when given another #{described_class}" do 74 | context "when the other domain name is fully qualified" do 75 | subject { described_class.new(domain_name) } 76 | 77 | context "and it matches the domain name" do 78 | it { expect(subject.include?(domain_name)).to be true } 79 | end 80 | 81 | context "but it doesn't match" do 82 | it { expect(subject.include?('foo.com')).to be false } 83 | end 84 | end 85 | 86 | context "when the other domain name is a wildcard" do 87 | subject { described_class.new(wildcard_name) } 88 | 89 | context "and it matches the wildcard domain name" do 90 | it { expect(subject.include?(wildcard_name)).to be true } 91 | end 92 | 93 | context "and shares the same domain name" do 94 | it { expect(subject.include?(domain_name)).to be true } 95 | end 96 | 97 | context "and it is a subdomain of the wildcard domain name" do 98 | it { expect(subject.include?("foo.#{domain_name}")).to be true } 99 | end 100 | 101 | context "but it doesn't match" do 102 | it { expect(subject.include?("foo.com")).to be false } 103 | end 104 | end 105 | end 106 | end 107 | 108 | describe "#to_s" do 109 | it "should return the #name" do 110 | expect(subject.to_s).to be subject.name 111 | end 112 | end 113 | 114 | describe "#to_str" do 115 | it "should return the #name" do 116 | expect(subject.to_str).to be subject.name 117 | end 118 | end 119 | 120 | describe "#inspect" do 121 | it "should include the class name and #name" do 122 | expect(subject.inspect).to be == "#<#{described_class}: #{subject.name}>" 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /spec/x509/extension_set_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'openssl' 3 | 4 | require 'sslyze/x509/extension_set' 5 | 6 | describe SSLyze::X509::ExtensionSet do 7 | let(:authority_key_identifier) do 8 | OpenSSL::X509::Extension.new("authorityKeyIdentifier", "keyid:3D:D3:50:A5:D6:A0:AD:EE:F3:4A:60:0A:65:D3:21:D4:F8:F8:D6:0F\n") 9 | end 10 | let(:subject_key_identifier) do 11 | OpenSSL::X509::Extension.new("subjectKeyIdentifier", "9F:62:7B:B2:88:0E:EE:1B:79:E0:69:24:E5:BA:3F:47:A6:0B:02:F0") 12 | end 13 | let(:subject_alt_name) do 14 | OpenSSL::X509::Extension.new("subjectAltName", "DNS:twitter.com, DNS:www.twitter.com") 15 | end 16 | let(:key_usage) do 17 | OpenSSL::X509::Extension.new("keyUsage", "Digital Signature, Key Encipherment", true) 18 | end 19 | let(:extended_key_usage) do 20 | OpenSSL::X509::Extension.new("extendedKeyUsage", "TLS Web Server Authentication, TLS Web Client Authentication") 21 | end 22 | let(:crl_distribution_points) do 23 | OpenSSL::X509::Extension.new("crlDistributionPoints", "\nFull Name:\n URI:http://crl3.digicert.com/sha2-ev-server-g1.crl\n\nFull Name:\n URI:http://crl4.digicert.com/sha2-ev-server-g1.crl\n") 24 | end 25 | let(:certificate_policies) do 26 | OpenSSL::X509::Extension.new("certificatePolicies", "Policy: 2.16.840.1.114412.2.1\n CPS: https://www.digicert.com/CPS\nPolicy: 2.23.140.1.1\n") 27 | end 28 | let(:authority_info_access) do 29 | OpenSSL::X509::Extension.new("authorityInfoAccess", "OCSP - URI:http://ocsp.digicert.com\nCA Issuers - URI:http://cacerts.digicert.com/DigiCertSHA2ExtendedValidationServerCA.crt\n") 30 | end 31 | let(:basic_constraints) do 32 | OpenSSL::X509::Extension.new("basicConstraints", "CA:FALSE", true) 33 | end 34 | 35 | let(:openssl_x509_extensions) do 36 | [ 37 | authority_key_identifier, 38 | subject_key_identifier, 39 | subject_alt_name, 40 | key_usage, 41 | extended_key_usage, 42 | crl_distribution_points, 43 | certificate_policies, 44 | authority_info_access, 45 | basic_constraints 46 | ] 47 | end 48 | 49 | subject { described_class.new(openssl_x509_extensions) } 50 | 51 | describe "#each" do 52 | context "when given a block" do 53 | it "must iterate over each wrapped Extension" do 54 | expect { |b| 55 | subject.each(&b) 56 | }.to yield_successive_args(*openssl_x509_extensions) 57 | end 58 | end 59 | 60 | context "when no block is given" do 61 | it "should return an Enumerator object" do 62 | expect(subject.each).to be_kind_of(Enumerator) 63 | end 64 | end 65 | end 66 | 67 | describe "#has?" do 68 | context "when the given key matches an extension's OID" do 69 | let(:key) { 'subjectAltName' } 70 | 71 | it { expect(subject.has?(key)).to be true } 72 | end 73 | 74 | context "when the given key matches no extension's OID" do 75 | it { expect(subject.has?("foo")).to be false } 76 | end 77 | end 78 | 79 | describe "#[]" do 80 | context "when the given key matches an extension's OID" do 81 | let(:key) { 'subjectAltName' } 82 | 83 | it "must return that extension" do 84 | expect(subject[key].oid).to be == key 85 | end 86 | end 87 | 88 | context "when the given key matches no extension's OID" do 89 | it "must return nil" do 90 | expect(subject["foo"]).to be nil 91 | end 92 | end 93 | end 94 | 95 | describe "#to_a" do 96 | it "should return the Array of extensions" do 97 | expect(subject.to_a).to be == openssl_x509_extensions 98 | end 99 | end 100 | 101 | describe "#basic_constraints" do 102 | context "when there is a 'basiConstraints' extension" do 103 | it do 104 | expect(subject.basic_constraints).to be_kind_of(SSLyze::X509::Extensions::BasicConstraints) 105 | end 106 | 107 | it "should find the extensions with the 'basicConstraints' OID" do 108 | expect(subject.basic_constraints.oid).to be == 'basicConstraints' 109 | end 110 | end 111 | 112 | context "when there is no 'basicConstraints' extension" do 113 | let(:openssl_x509_extensions) { super() - [basic_constraints] } 114 | 115 | it { expect(subject.basic_constraints).to be nil } 116 | end 117 | end 118 | 119 | describe "#certificate_policies" do 120 | context "when there is a 'certificatePolicies' extension" do 121 | it do 122 | expect(subject.certificate_policies).to be_kind_of(SSLyze::X509::Extensions::CertificatePolicies) 123 | end 124 | 125 | it "should find the extensions with the 'certificatePolicies' OID" do 126 | expect(subject.certificate_policies.oid).to be == 'certificatePolicies' 127 | end 128 | end 129 | 130 | context "when there is no 'certificatePolicies' extension" do 131 | let(:openssl_x509_extensions) { super() - [certificate_policies] } 132 | 133 | it { expect(subject.certificate_policies).to be nil } 134 | end 135 | end 136 | 137 | describe "#crl_distribution_points" do 138 | context "when there is a 'crlDistributionPoints' extension" do 139 | it do 140 | expect(subject.crl_distribution_points).to be_kind_of(SSLyze::X509::Extensions::CRLDistributionPoints) 141 | end 142 | 143 | it "should find the extensions with the 'crlDistributionPoints' OID" do 144 | expect(subject.crl_distribution_points.oid).to be == 'crlDistributionPoints' 145 | end 146 | end 147 | 148 | context "when there is no 'crlDistributionPoints' extension" do 149 | let(:openssl_x509_extensions) { super() - [crl_distribution_points] } 150 | 151 | it { expect(subject.crl_distribution_points).to be nil } 152 | end 153 | end 154 | 155 | describe "#extended_key_usage" do 156 | context "when there is a 'extendedKeyUsage' extension" do 157 | it do 158 | expect(subject.extended_key_usage).to be_kind_of(SSLyze::X509::Extensions::ExtendedKeyUsage) 159 | end 160 | 161 | it "should find the extensions with the 'extendedKeyUsage' OID" do 162 | expect(subject.extended_key_usage.oid).to be == 'extendedKeyUsage' 163 | end 164 | end 165 | 166 | context "when there is no 'extendedKeyUsage' extension" do 167 | let(:openssl_x509_extensions) { super() - [extended_key_usage] } 168 | 169 | it { expect(subject.extended_key_usage).to be nil } 170 | end 171 | end 172 | 173 | describe "#key_usage" do 174 | context "when there is a 'keyUsage' extension" do 175 | it do 176 | expect(subject.key_usage).to be_kind_of(SSLyze::X509::Extensions::KeyUsage) 177 | end 178 | 179 | it "should find the extensions with the 'keyUsage' OID" do 180 | expect(subject.key_usage.oid).to be == 'keyUsage' 181 | end 182 | end 183 | 184 | context "when there is no 'keyUsage' extension" do 185 | let(:openssl_x509_extensions) { super() - [key_usage] } 186 | 187 | it { expect(subject.key_usage).to be nil } 188 | end 189 | end 190 | 191 | describe "#subject_alt_name" do 192 | context "when there is a 'subjectAltName' extension" do 193 | it do 194 | expect(subject.subject_alt_name).to be_kind_of(SSLyze::X509::Extensions::SubjectAltName) 195 | end 196 | 197 | it "should find the extensions with the 'subjectAltName' OID" do 198 | expect(subject.subject_alt_name.oid).to be == 'subjectAltName' 199 | end 200 | end 201 | 202 | context "when there is no 'subjectAltName' extension" do 203 | let(:openssl_x509_extensions) { super() - [subject_alt_name] } 204 | 205 | it { expect(subject.subject_alt_name).to be nil } 206 | end 207 | end 208 | end 209 | -------------------------------------------------------------------------------- /spec/x509/extension_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'openssl' 3 | 4 | require 'sslyze/x509/extension' 5 | 6 | describe SSLyze::X509::Extension do 7 | let(:oid) { 'basicConstraints' } 8 | let(:value) { 'CA:FALSE' } 9 | let(:critical) { true } 10 | 11 | let(:openssl_x509_extension) do 12 | OpenSSL::X509::Extension.new(oid,value,critical) 13 | end 14 | 15 | subject { described_class.new(openssl_x509_extension) } 16 | 17 | describe "#critical?" do 18 | it "should return the underlying #critical? value" do 19 | expect(subject.critical?).to be == openssl_x509_extension.critical? 20 | end 21 | end 22 | 23 | describe "#oid" do 24 | it "should return the underlying #oid value" do 25 | expect(subject.oid).to be == openssl_x509_extension.oid 26 | end 27 | end 28 | 29 | describe "#value" do 30 | it "should return the underlying #value value" do 31 | expect(subject.value).to be == openssl_x509_extension.value 32 | end 33 | end 34 | 35 | describe "#to_a" do 36 | it "should return the underlying #to_a value" do 37 | expect(subject.to_a).to be == openssl_x509_extension.to_a 38 | end 39 | end 40 | 41 | describe "#to_der" do 42 | it "should return the underlying #to_der value" do 43 | expect(subject.to_der).to be == openssl_x509_extension.to_der 44 | end 45 | end 46 | 47 | describe "#to_h" do 48 | it "should return the underlying #to_h value" do 49 | expect(subject.to_h).to be == openssl_x509_extension.to_h 50 | end 51 | end 52 | 53 | describe "#to_s" do 54 | it "should return the underlying #to_s value" do 55 | expect(subject.to_s).to be == openssl_x509_extension.to_s 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/x509/extensions/basic_constraints_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'openssl' 3 | 4 | require 'sslyze/x509/extensions/basic_constraints' 5 | 6 | describe SSLyze::X509::Extensions::BasicConstraints do 7 | let(:ca) { 'TRUE' } 8 | let(:pathlen) { 1 } 9 | let(:value) { "CA:#{ca}, pathlen:#{pathlen}" } 10 | let(:openssl_x509_extension) do 11 | OpenSSL::X509::Extension.new('basicConstraints', value, true) 12 | end 13 | 14 | subject { described_class.new(openssl_x509_extension) } 15 | 16 | describe "#ca?" do 17 | context "when 'CA:TRUE' is present" do 18 | let(:ca) { 'TRUE' } 19 | 20 | it { expect(subject.ca?).to be true } 21 | end 22 | 23 | context "when 'CA:FALSE' is present" do 24 | let(:ca) { 'FALSE' } 25 | 26 | it { expect(subject.ca?).to be false } 27 | end 28 | 29 | context "when 'CA:' is omitted" do 30 | let(:value) { 'pathlen:0' } 31 | 32 | it { expect(subject.ca?).to be nil } 33 | end 34 | end 35 | 36 | describe "#path_length" do 37 | it "should parse the decimal following 'pathlen:'" do 38 | expect(subject.path_length).to be == pathlen 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/x509/extensions/certificate_policies_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'openssl' 3 | 4 | require 'sslyze/x509/extensions/certificate_policies' 5 | 6 | describe SSLyze::X509::Extensions::CertificatePolicies do 7 | let(:policy) { 'X509v3 Any Policy' } 8 | let(:cps) { URI('https://www.digicert.com/CPS') } 9 | let(:value) { "Policy: #{policy}\n CPS: #{cps}\n" } 10 | 11 | let(:openssl_x509_extension) do 12 | OpenSSL::X509::Extension.new('certificatePolicies', value) 13 | end 14 | 15 | subject { described_class.new(openssl_x509_extension) } 16 | 17 | describe "#policies" do 18 | it "must yield #{described_class::Policy} objects" do 19 | expect(subject.policies).to all(be_kind_of(described_class::Policy)) 20 | end 21 | 22 | it "should capture the text following 'Policy: '" do 23 | expect(subject.policies.first.policy).to be == policy 24 | end 25 | 26 | it "should capture the URI following ' CPS: '" do 27 | expect(subject.policies.first.cps).to be == cps 28 | end 29 | end 30 | 31 | describe "#each" do 32 | it "should yield #{described_class::Policy} objects" do 33 | expect { |b| 34 | subject.each(&b) 35 | }.to yield_successive_args(*Array.new(subject.length,described_class::Policy)) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/x509/extensions/crl_distribution_points_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'openssl' 3 | 4 | require 'sslyze/x509/extensions/crl_distribution_points' 5 | 6 | describe SSLyze::X509::Extensions::CRLDistributionPoints do 7 | let(:uri1) { URI('http://crl3.digicert.com/sha2-ha-server-g5.crl') } 8 | let(:uri2) { URI('http://crl4.digicert.com/sha2-ha-server-g5.crl') } 9 | let(:value) do 10 | %{ 11 | Full Name: 12 | URI:#{uri1} 13 | 14 | Full Name: 15 | URI:#{uri2} 16 | } 17 | end 18 | 19 | let(:openssl_x509_extension) do 20 | OpenSSL::X509::Extension.new('crlDistributionPoints',value) 21 | end 22 | 23 | subject { described_class.new(openssl_x509_extension) } 24 | 25 | describe "#uris" do 26 | it "should parse each 'URI:' value" do 27 | expect(subject.uris).to be == [uri1, uri2] 28 | end 29 | end 30 | 31 | describe "#each" do 32 | it "should yield each parsed 'URI:' value" do 33 | expect { |b| 34 | subject.each(&b) 35 | }.to yield_successive_args(uri1, uri2) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/x509/extensions/extended_key_usage_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'openssl' 3 | 4 | require 'sslyze/x509/extensions/extended_key_usage' 5 | 6 | describe SSLyze::X509::Extensions::ExtendedKeyUsage do 7 | let(:uses) do 8 | [ 9 | 'TLS Web Server Authentication', 10 | 'TLS Web Client Authentication' 11 | ] 12 | end 13 | let(:value) { uses.join(', ') } 14 | 15 | let(:openssl_x509_extension) do 16 | OpenSSL::X509::Extension.new('extendedKeyUsage', value) 17 | end 18 | 19 | subject { described_class.new(openssl_x509_extension) } 20 | 21 | describe "#uses" do 22 | it "should parse the comma deliminated value" do 23 | expect(subject.uses).to be == uses 24 | end 25 | end 26 | 27 | describe "#each" do 28 | it "should yield each parsed use" do 29 | expect { |b| 30 | subject.each(&b) 31 | }.to yield_successive_args(*uses) 32 | end 33 | end 34 | 35 | describe "#tls_web_server_authentication?" do 36 | context "when 'TLS Web Server Authentication' is present" do 37 | it { expect(subject.tls_web_server_authentication?).to be true } 38 | end 39 | 40 | context "when 'TLS Web Server Authentication' is not present" do 41 | let(:uses) { super() - ['TLS Web Server Authentication'] } 42 | 43 | it { expect(subject.tls_web_server_authentication?).to be false } 44 | end 45 | end 46 | 47 | describe "#tls_web_client_authentication?" do 48 | context "when 'TLS Web Client Authentication' is present" do 49 | it { expect(subject.tls_web_client_authentication?).to be true } 50 | end 51 | 52 | context "when 'TLS Web Client Authentication' is not present" do 53 | let(:uses) { super() - ['TLS Web Client Authentication'] } 54 | 55 | it { expect(subject.tls_web_client_authentication?).to be false } 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/x509/extensions/key_usage_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'openssl' 3 | 4 | require 'sslyze/x509/extensions/key_usage' 5 | 6 | describe SSLyze::X509::Extensions::KeyUsage do 7 | let(:uses) do 8 | [ 9 | 'Key Encipherment', 10 | 'Digital Signature', 11 | 'CRL Sign', 12 | 'Certificate Sign' 13 | ] 14 | end 15 | let(:value) { uses.join(', ') } 16 | 17 | let(:openssl_x509_extension) do 18 | OpenSSL::X509::Extension.new('keyUsage', value) 19 | end 20 | 21 | subject { described_class.new(openssl_x509_extension) } 22 | 23 | describe "#uses" do 24 | it "should parse the comma deliminated value" do 25 | expect(subject.uses).to be == uses 26 | end 27 | end 28 | 29 | describe "#each" do 30 | it "should yield each parsed use" do 31 | expect { |b| 32 | subject.each(&b) 33 | }.to yield_successive_args(*uses) 34 | end 35 | end 36 | 37 | describe "#key_encipherment?" do 38 | context "when 'Key Encipherment' is present" do 39 | it { expect(subject.key_encipherment?).to be true } 40 | end 41 | 42 | context "when 'Key Encipherment' is not present" do 43 | let(:uses) { super() - ['Key Encipherment'] } 44 | 45 | it { expect(subject.key_encipherment?).to be false } 46 | end 47 | end 48 | 49 | describe "#digital_signature?" do 50 | context "when 'Digital Signature' is present" do 51 | it { expect(subject.digital_signature?).to be true } 52 | end 53 | 54 | context "when 'Digital Signature' is not present" do 55 | let(:uses) { super() - ['Digital Signature'] } 56 | 57 | it { expect(subject.digital_signature?).to be false } 58 | end 59 | end 60 | 61 | describe "#crl_sign?" do 62 | context "when 'CRL Sign' is present" do 63 | it { expect(subject.crl_sign?).to be true } 64 | end 65 | 66 | context "when 'CRL Sign' is not present" do 67 | let(:uses) { super() - ['CRL Sign'] } 68 | 69 | it { expect(subject.crl_sign?).to be false } 70 | end 71 | end 72 | 73 | describe "#certificate_sign?" do 74 | context "when 'Certificate Sign' is present" do 75 | it { expect(subject.certificate_sign?).to be true } 76 | end 77 | 78 | context "when 'Certificate Sign' is not present" do 79 | let(:uses) { super() - ['Certificate Sign'] } 80 | 81 | it { expect(subject.certificate_sign?).to be false } 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /spec/x509/extensions/subject_alt_name_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'openssl' 3 | 4 | require 'sslyze/x509/extensions/subject_alt_name' 5 | 6 | describe SSLyze::X509::Extensions::SubjectAltName do 7 | let(:domains) do 8 | %w[ 9 | example.com 10 | foo.example.com 11 | bar.example.com 12 | ] 13 | end 14 | let(:dns_names) do 15 | domains.map { |name| "DNS:#{name}" } 16 | end 17 | 18 | let(:ips) do 19 | %w[ 20 | 127.0.0.1 21 | 10.0.0.1 22 | ] 23 | end 24 | let(:ip_names) do 25 | ips.map { |name| "IP:#{name}" } 26 | end 27 | 28 | let(:uris) do 29 | %w[ 30 | https://foo.com/ 31 | https://bar.com/ 32 | ] 33 | end 34 | let(:uri_names) do 35 | uris.map { |name| "URI:#{name}" } 36 | end 37 | 38 | let(:email_addresses) do 39 | %w[ 40 | foo@example.com 41 | bar@example.com 42 | ] 43 | end 44 | let(:email_names) do 45 | email_addresses.map { |name| "email:#{name}" } 46 | end 47 | 48 | let(:names) { dns_names + ip_names + uri_names + email_names } 49 | let(:value) { names.join(', ') } 50 | 51 | let(:openssl_x509_extension) do 52 | OpenSSL::X509::Extension.new('subjectAltName', value) 53 | end 54 | 55 | subject { described_class.new(openssl_x509_extension) } 56 | 57 | describe "#each" do 58 | context "when a block is given" do 59 | context "when value contains 'DNS:' names" do 60 | let(:names) { dns_names } 61 | 62 | it "should yield (:dns, name) tuples" do 63 | expect { |b| 64 | subject.each(&b) 65 | }.to yield_successive_args(*domains.map { |domain| [:dns, domain] }) 66 | end 67 | end 68 | 69 | context "when value contains 'IP:' names" do 70 | let(:names) { ip_names } 71 | 72 | it "should yield (:ip, name) tuples" do 73 | expect { |b| 74 | subject.each(&b) 75 | }.to yield_successive_args(*ips.map { |ip| [:ip, ip] }) 76 | end 77 | end 78 | 79 | context "when value contains 'URI:' names" do 80 | let(:names) { uri_names } 81 | 82 | it "should yield (:uri, name) tuples" do 83 | expect { |b| 84 | subject.each(&b) 85 | }.to yield_successive_args(*uris.map { |uri| [:uri, uri] }) 86 | end 87 | end 88 | 89 | context "when value contains 'email:' names" do 90 | let(:names) { email_names } 91 | 92 | it "should yield (:email, name) tuples" do 93 | expect { |b| 94 | subject.each(&b) 95 | }.to yield_successive_args(*email_addresses.map { |email| [:email, email] }) 96 | end 97 | end 98 | 99 | context "when value contains 'RID:' names" 100 | context "when value contains 'dirName:' names" 101 | context "when value contains 'otherName:' names" 102 | end 103 | 104 | context "when no block is given" do 105 | it "should return return an Enumerator" do 106 | expect(subject.each).to be_kind_of(Enumerator) 107 | end 108 | end 109 | end 110 | 111 | describe "#dns" do 112 | it "should return only the 'DNS:' names" do 113 | expect(subject.dns).to be == domains 114 | end 115 | end 116 | 117 | describe "#ip" do 118 | it "should return only the 'IP:' names" do 119 | expect(subject.ip.map(&:to_s)).to be == ips 120 | end 121 | 122 | it "should parse each 'IP:' name as an IPAddr" do 123 | expect(subject.ip).to all(be_kind_of(IPAddr)) 124 | end 125 | end 126 | 127 | describe "#uri" do 128 | it "should return only the 'URI:' names" do 129 | expect(subject.uri.map(&:to_s)).to be == uris 130 | end 131 | 132 | it "should parse each 'URI:' name as an URI" do 133 | expect(subject.uri).to all(be_kind_of(URI::Generic)) 134 | end 135 | end 136 | 137 | describe "#email" do 138 | it "should return only the 'email:' names" do 139 | expect(subject.email).to be == email_addresses 140 | end 141 | end 142 | 143 | describe "#rid" 144 | describe "#dir_name" 145 | describe "#other_name" 146 | end 147 | -------------------------------------------------------------------------------- /spec/x509/name_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'openssl' 3 | 4 | require 'sslyze/x509/name' 5 | 6 | describe SSLyze::X509::Name do 7 | let(:country_name) { 'US' } 8 | let(:state_name) { 'California' } 9 | let(:location_name) { 'San Francisco' } 10 | let(:organization_name) { 'Twitter, Inc.' } 11 | let(:organizational_unit_name) { 'Twitter Security' } 12 | let(:common_name) { 'twitter.com' } 13 | 14 | let(:entries) do 15 | [ 16 | ["C", country_name, 19], 17 | ["ST", state_name, 19], 18 | ["L", location_name, 19], 19 | ["O", organization_name, 19], 20 | ["OU", organizational_unit_name, 19], 21 | ["CN", common_name, 19] 22 | ] 23 | end 24 | let(:openssl_x509_name) { OpenSSL::X509::Name.new(entries) } 25 | 26 | subject { described_class.new(openssl_x509_name) } 27 | 28 | describe "#each" do 29 | it "should iterate over the entries" do 30 | expect { |b| 31 | subject.each(&b) 32 | }.to yield_successive_args(*entries) 33 | end 34 | end 35 | 36 | describe "#[]" do 37 | it "should return the value for the given OID key" do 38 | expect(subject['C']).to be == country_name 39 | end 40 | 41 | context "when the OID key cannot be found" do 42 | it { expect(subject['foo']).to be nil } 43 | end 44 | end 45 | 46 | describe "#country_name" do 47 | it "should return the 'C' entry" do 48 | expect(subject.country_name).to be == country_name 49 | end 50 | end 51 | 52 | describe "#state_name" do 53 | it "should return the 'ST' entry" do 54 | expect(subject.state_name).to be == state_name 55 | end 56 | end 57 | 58 | describe "#location_name" do 59 | it "should return the 'L' entry" do 60 | expect(subject.location_name).to be == location_name 61 | end 62 | end 63 | 64 | describe "#organization_name" do 65 | it "should return the 'O' entry" do 66 | expect(subject.organization_name).to be == organization_name 67 | end 68 | end 69 | 70 | describe "#organizational_unit_name" do 71 | it "should return the 'OU' entry" do 72 | expect(subject.organizational_unit_name).to be == organizational_unit_name 73 | end 74 | end 75 | 76 | describe "#common_name" do 77 | it do 78 | expect(subject.common_name).to be_kind_of(SSLyze::X509::Domain) 79 | end 80 | 81 | it "should be equal to the 'CN' entry" do 82 | expect(subject.common_name.name).to be == common_name 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/xml/certinfo/certificate/public_key_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/certinfo/certificate/public_key' 4 | 5 | describe SSLyze::XML::Certinfo::Certificate::PublicKey do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target/certinfo/receivedCertificateChain/certificate/publicKey' } 9 | 10 | subject { described_class.new(xml.at(xpath)) } 11 | 12 | describe "#algorithm" do 13 | context "when the algorithm attribute is RSA" do 14 | let(:xpath) { "#{super()}[@algorithm='RSA']" } 15 | 16 | it { expect(subject.algorithm).to be :RSA } 17 | end 18 | 19 | context "when the algorithm attribute is DSA" do 20 | let(:xpath) { "#{super()}[@algorithm='DSA']" } 21 | 22 | pending "need to find a target with a DSA public key" do 23 | expect(subject.algorithm).to be :DSA 24 | end 25 | end 26 | 27 | context "when the algorithm attribute is EllipticCurve" do 28 | let(:xpath) { "#{super()}[@algorithm='EllipticCurve']" } 29 | 30 | it { expect(subject.algorithm).to be :EC } 31 | end 32 | end 33 | 34 | describe "#size" do 35 | let(:expected_size) { 2048 } 36 | let(:xpath) { "#{super()}[@size=#{expected_size}]" } 37 | 38 | it "should parse the size attribute" do 39 | expect(subject.size).to be expected_size 40 | end 41 | end 42 | 43 | describe "#curve" do 44 | context "when the curve attribute is present" do 45 | let(:expected_curve) { :secp256r1 } 46 | let(:xpath) { "#{super()}[@curve='#{expected_curve}']" } 47 | 48 | it { expect(subject.curve).to be(expected_curve) } 49 | end 50 | 51 | context "when the curve attribute is missing" do 52 | let(:xpath) { "#{super()}[not(@curve)]" } 53 | 54 | it { expect(subject.curve).to be nil } 55 | end 56 | end 57 | 58 | describe "#exponent" do 59 | context "when the exponent attribute is present" do 60 | let(:xpath) { "#{super()}[@exponent]" } 61 | 62 | it { expect(subject.exponent).to be 65537 } 63 | end 64 | 65 | context "when the exponent attribute is missing" do 66 | let(:xpath) { "#{super()}[not(@exponent)]" } 67 | 68 | it { expect(subject.exponent).to be nil } 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/xml/certinfo/certificate_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/certinfo/certificate' 4 | 5 | describe SSLyze::XML::Certinfo::Certificate do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target/certinfo/receivedCertificateChain/certificate' } 9 | 10 | subject { described_class.new(xml.at(xpath)) } 11 | 12 | describe "#sha1_fingerprint" do 13 | let(:expected_sha1) { 'd79f076110b39293e349ac89845b0380c19e2f8b' } 14 | 15 | let(:xpath) { "#{super()}[@sha1Fingerprint='#{expected_sha1}']" } 16 | 17 | it "should parse the 'sha1Fingerprint' attribute" do 18 | expect(subject.sha1_fingerprint).to be == expected_sha1 19 | end 20 | end 21 | 22 | describe "#hpkp_sha256_pin" do 23 | let(:expected_hpkp_sha256) { 'pL1+qb9HTMRZJmuC/bB/ZI9d302BYrrqiVuRyW+DGrU=' } 24 | 25 | let(:xpath) { "#{super()}[@hpkpSha256Pin='#{expected_hpkp_sha256}']" } 26 | 27 | it "should parse the 'hpkpSha256Pin' attribute" do 28 | expect(subject.hpkp_sha256_pin).to be == expected_hpkp_sha256 29 | end 30 | end 31 | 32 | describe "#as_pem" do 33 | let(:host) { 'github.com' } 34 | let(:host_pem) do 35 | %{-----BEGIN CERTIFICATE----- 36 | MIIHeTCCBmGgAwIBAgIQC/20CQrXteZAwwsWyVKaJzANBgkqhkiG9w0BAQsFADB1 37 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 38 | d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk 39 | IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE2MDMxMDAwMDAwMFoXDTE4MDUxNzEy 40 | MDAwMFowgf0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB 41 | BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF 42 | Ewc1MTU3NTUwMSQwIgYDVQQJExs4OCBDb2xpbiBQIEtlbGx5LCBKciBTdHJlZXQx 43 | DjAMBgNVBBETBTk0MTA3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p 44 | YTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMu 45 | MRMwEQYDVQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 46 | CgKCAQEA54hc8pZclxgcupjiA/F/OZGRwm/ZlucoQGTNTKmBEgNsrn/mxhngWmPw 47 | bAvUaLP//T79Jc+1WXMpxMiz9PK6yZRRFuIo0d2bx423NA6hOL2RTtbnfs+y0PFS 48 | /YTpQSelTuq+Fuwts5v6aAweNyMcYD0HBybkkdosFoDccBNzJ92Ac8I5EVDUc3Or 49 | /4jSyZwzxu9kdmBlBzeHMvsqdH8SX9mNahXtXxRpwZnBiUjw36PgN+s9GLWGrafd 50 | 02T0ux9Yzd5ezkMxukqEAQ7AKIIijvaWPAJbK/52XLhIy2vpGNylyni/DQD18bBP 51 | T+ZG1uv0QQP9LuY/joO+FKDOTler4wIDAQABo4IDejCCA3YwHwYDVR0jBBgwFoAU 52 | PdNQpdagre7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFIhcSGcZzKB2WS0RecO+oqyH 53 | IidbMCUGA1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1Ud 54 | DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0f 55 | BG4wbDA0oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2Vy 56 | dmVyLWcxLmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTIt 57 | ZXYtc2VydmVyLWcxLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG/WwCATAqMCgGCCsG 58 | AQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGI 59 | BggrBgEFBQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0 60 | LmNvbTBSBggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Rp 61 | Z2lDZXJ0U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMB 62 | Af8EAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgCkuQmQtBhYFIe7E6LM 63 | Z3AKPDWYBPkb37jjd80OyA3cEAAAAVNhieoeAAAEAwBHMEUCIQCHHSEY/ROK2/sO 64 | ljbKaNEcKWz6BxHJNPOtjSyuVnSn4QIgJ6RqvYbSX1vKLeX7vpnOfCAfS2Y8lB5R 65 | NMwk6us2QiAAdgBo9pj4H2SCvjqM7rkoHUz8cVFdZ5PURNEKZ6y7T0/7xAAAAVNh 66 | iennAAAEAwBHMEUCIQDZpd5S+3to8k7lcDeWBhiJASiYTk2rNAT26lVaM3xhWwIg 67 | NUqrkIODZpRg+khhp8ag65B8mu0p4JUAmkRDbiYnRvYAdwBWFAaaL9fC7NP14b1E 68 | sj7HRna5vJkRXMDvlJhV1onQ3QAAAVNhieqZAAAEAwBIMEYCIQDnm3WStlvE99GC 69 | izSx+UGtGmQk2WTokoPgo1hfiv8zIAIhAPrYeXrBgseA9jUWWoB4IvmcZtshjXso 70 | nT8MIG1u1zF8MA0GCSqGSIb3DQEBCwUAA4IBAQCLbNtkxuspqycq8h1EpbmAX0wM 71 | 5DoW7hM/FVdz4LJ3Kmftyk1yd8j/PSxRrAQN2Mr/frKeK8NE1cMji32mJbBqpWtK 72 | /+wC+avPplBUbNpzP53cuTMF/QssxItPGNP5/OT9Aj1BxA/NofWZKh4ufV7cz3pY 73 | RDS4BF+EEFQ4l5GY+yp4WJA/xSvYsTHWeWxRD1/nl62/Rd9FN2NkacRVozCxRVle 74 | FrBHTFxqIP6kDnxiLElBrZngtY07ietaYZVLQN/ETyqLQftsf8TecwTklbjvm8NT 75 | JqbaIVifYwqwNN+4lRxS3F5lNlA/il12IOgbRioLI62o8G0DaEUQgHNf8vSG 76 | -----END CERTIFICATE----- 77 | } 78 | end 79 | 80 | let(:xpath) { "/document/results/target[@host='#{host}']/certinfo/receivedCertificateChain/certificate" } 81 | 82 | it "should parse the asPEM element" do 83 | expect(subject.as_pem).to be == host_pem 84 | end 85 | end 86 | 87 | describe "#x509" do 88 | it do 89 | expect(subject.x509).to be_kind_of(OpenSSL::X509::Certificate) 90 | end 91 | end 92 | 93 | describe "#extensions" do 94 | it do 95 | expect(subject.extensions).to be_kind_of(SSLyze::X509::ExtensionSet) 96 | end 97 | end 98 | 99 | describe "#issuer" do 100 | it do 101 | expect(subject.issuer).to be_a(SSLyze::X509::Name) 102 | end 103 | end 104 | 105 | describe "#not_after" do 106 | it do 107 | expect(subject.not_after).to be_kind_of(Time) 108 | end 109 | end 110 | 111 | describe "#not_before" do 112 | it do 113 | expect(subject.not_before).to be_kind_of(Time) 114 | end 115 | end 116 | 117 | describe "#public_key" do 118 | it do 119 | expect(subject.public_key).to be_a(described_class::PublicKey) 120 | end 121 | end 122 | 123 | describe "#serial" do 124 | it do 125 | expect(subject.serial).to be_a(OpenSSL::BN) 126 | end 127 | end 128 | 129 | describe "#signature_algorithm" do 130 | it do 131 | expect(subject.signature_algorithm).to be == 'sha256WithRSAEncryption' 132 | end 133 | end 134 | 135 | describe "#subject" do 136 | it do 137 | expect(subject.subject).to be_a(SSLyze::X509::Name) 138 | end 139 | end 140 | 141 | describe "#version" do 142 | it do 143 | expect(subject.version).to be 2 144 | end 145 | end 146 | 147 | describe "#==" do 148 | context "when the #as_pem matches" do 149 | let(:other) { described_class.new(xml.at("#{xpath}[1]")) } 150 | 151 | it { expect(subject == other).to be true } 152 | end 153 | 154 | context "when the #as_pem do not match" do 155 | let(:other) { described_class.new(xml.at("#{xpath}[2]")) } 156 | 157 | it { expect(subject == other).to be false } 158 | end 159 | 160 | context "when the classes do not match" do 161 | let(:other) { Object.new } 162 | 163 | it { expect(subject == other).to be false } 164 | end 165 | end 166 | end 167 | -------------------------------------------------------------------------------- /spec/xml/certinfo/certificate_validation/hostname_validation_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/certinfo/certificate_validation/hostname_validation' 4 | 5 | describe SSLyze::XML::Certinfo::CertificateValidation::HostnameValidation do 6 | include_examples "XML specs" 7 | 8 | subject do 9 | described_class.new(xml.at('/document/results/target/certinfo/certificateValidation/hostnameValidation')) 10 | end 11 | 12 | describe "#certificate_matches_server_hostname?" do 13 | it "should return a Boolean value" do 14 | expect(subject.certificate_matches_server_hostname?).to be true 15 | end 16 | end 17 | 18 | describe "#server_hostname" do 19 | it "should return the serverHostname attribute" do 20 | expect(subject.server_hostname).to match(/^(?:\w+\.)?\w+\.com$/) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/xml/certinfo/certificate_validation/path_validation_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/certinfo/certificate_validation/path_validation' 4 | 5 | describe SSLyze::XML::Certinfo::CertificateValidation::PathValidation do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target/certinfo/certificateValidation/pathValidation' } 9 | 10 | subject do 11 | described_class.new(xml.at(xpath)) 12 | end 13 | 14 | describe "#is_extended_validation_certificate?" do 15 | context "when the 'isExtendedValidationCertificate' XML attribute is present" do 16 | subject do 17 | described_class.new(xml.at("#{xpath}[@isExtendedValidationCertificate]")) 18 | end 19 | 20 | it "should return a Boolean type" do 21 | expect(subject.is_extended_validation_certificate?).to be(false).or(be(true)) 22 | end 23 | end 24 | 25 | context "when the 'isExtendedValidationCertificate' XML attribute is omitted" do 26 | subject do 27 | described_class.new(xml.at("#{xpath}[not(@isExtendedValidationCertificate)]")) 28 | end 29 | 30 | it { expect(subject.is_extended_validation_certificate?).to be nil } 31 | end 32 | end 33 | 34 | describe "#trust_store_version" do 35 | it "must return the 'trustStoreVersion' XML attribute" do 36 | expect(subject.trust_store_version).to_not be_empty 37 | end 38 | end 39 | 40 | describe "#using_trust_store" do 41 | let(:trust_stores) { %w[Android iOS macOS Mozilla Windows] } 42 | 43 | it "must return the 'usingTrustStore' XML attribute" do 44 | expect(trust_stores).to include(subject.using_trust_store) 45 | end 46 | end 47 | 48 | describe "#validation_result" do 49 | context "when the 'validationResult' attribute is set" do 50 | subject do 51 | described_class.new(xml.at("#{xpath}[@validationResult]")) 52 | end 53 | 54 | it "must return the 'validationResult' XML element" do 55 | expect(subject.validation_result).to be == :ok 56 | end 57 | end 58 | 59 | context "when the 'validationResult' attribute is not set" do 60 | subject do 61 | described_class.new(xml.at("#{xpath}[not(@validationResult)]")) 62 | end 63 | 64 | it "must return the 'validationResult' XML element" do 65 | pending "need an example without the 'validationResult' XML attribute" 66 | 67 | expect(subject.validation_result).to be nil 68 | end 69 | end 70 | end 71 | 72 | describe "#ok?" do 73 | context "when the 'validationResult' attribute is set" do 74 | context "and is 'ok'" do 75 | subject do 76 | described_class.new(xml.at("#{xpath}[@validationResult=\"ok\"]")) 77 | end 78 | 79 | it { expect(subject.ok?).to be true } 80 | end 81 | 82 | context "but is not 'ok'" do 83 | subject do 84 | described_class.new(xml.at("#{xpath}[not(@validationResult=\"ok\")]")) 85 | end 86 | 87 | it do 88 | pending "need an example where the 'validationResult' isn't 'ok'" 89 | 90 | expect(subject.ok?).to be false 91 | end 92 | end 93 | end 94 | 95 | context "when the 'validationResult' attribute is not set" do 96 | subject do 97 | described_class.new(xml.at("#{xpath}[not(@validationResult)]")) 98 | end 99 | 100 | it "must return the 'validationResult' XML element" do 101 | pending "need an example without the 'validationResult' XML attribute" 102 | 103 | expect(subject.ok?).to be nil 104 | end 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /spec/xml/certinfo/certificate_validation/verified_certificate_chain_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/certinfo/certificate_validation/verified_certificate_chain' 4 | 5 | describe SSLyze::XML::Certinfo::CertificateValidation::VerifiedCertificateChain do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target/certinfo/certificateValidation/verifiedCertificateChain' } 9 | 10 | subject { described_class.new(xml.at(xpath)) } 11 | 12 | describe "#has_sha1_signed_certificate?" do 13 | it "should return a Boolean value" do 14 | expect(subject.has_sha1_signed_certificate?).to be(true).or(be(false)) 15 | end 16 | end 17 | 18 | describe "#each_certificate" do 19 | context "when at least one 'certificate' XML child exists" do 20 | let(:xpath) { "#{super()}[certificate]" } 21 | let(:xpath_count) { xml.at(xpath).xpath('certificate').count } 22 | 23 | it "should yield successive Certificate objects" do 24 | expect { |b| 25 | subject.each_certificate(&b) 26 | }.to yield_successive_args( 27 | *Array.new(xpath_count,SSLyze::XML::Certinfo::Certificate) 28 | ) 29 | end 30 | end 31 | 32 | context "when no 'certificate' XML children exist" do 33 | let(:xpath) { "#{super()}[not(./certificate)]" } 34 | 35 | it "should not yield control" do 36 | pending "need an example with no 'certificate' XML children" 37 | 38 | expect { |b| 39 | subject.each_certificate(&b) 40 | }.to_not yield_control 41 | end 42 | end 43 | end 44 | 45 | describe "#certificates" do 46 | context "when at least one 'certificate' XML child exists" do 47 | let(:xpath) { "#{super()}[certificate]" } 48 | let(:xpath_count) { xml.at(xpath).xpath('certificate').count } 49 | 50 | it do 51 | expect(subject.certificates).to be_a(Array).and( 52 | all(be_kind_of(SSLyze::XML::Certinfo::Certificate)) 53 | ) 54 | end 55 | end 56 | 57 | context "when no 'certificate' XML children exist" do 58 | let(:xpath) { "#{super()}[not(./certificate)]" } 59 | 60 | it do 61 | pending "need an example with no 'certificate' XML children" 62 | 63 | expect(subject.certificates).to be_empty 64 | end 65 | end 66 | end 67 | 68 | describe "#leaf" do 69 | context "when at least one 'certificate' XML child exists" do 70 | let(:xpath) { "#{super()}[certificate]" } 71 | 72 | it do 73 | expect(subject.leaf).to be_a(SSLyze::XML::Certinfo::Certificate) 74 | end 75 | 76 | it "should select the first 'certificate' XML child" do 77 | expect(subject.leaf).to be == \ 78 | SSLyze::XML::Certinfo::Certificate.new(xml.at("#{xpath}/certificate[1]")) 79 | end 80 | end 81 | 82 | context "when no 'certificate' XML children exist" do 83 | let(:xpath) { "#{super()}[not(./certificate)]" } 84 | 85 | it do 86 | pending "need an example with no 'certificate' XML children" 87 | 88 | expect(subject.leaf).to be nil 89 | end 90 | end 91 | end 92 | 93 | describe "#each_intermediate" do 94 | context "when there are more than two 'certificate' XML children" do 95 | let(:xpath) { "#{super()}[count(certificate) > 2]" } 96 | 97 | it "should yield the intermediate certificates" do 98 | expect { |b| 99 | subject.each_intermediate(&b) 100 | }.to yield_successive_args( 101 | SSLyze::XML::Certinfo::Certificate 102 | ) 103 | end 104 | end 105 | 106 | context "when there are two or fewer 'certificate' XML children" do 107 | let(:xpath) { "#{super()}[count(certificate) <= 2]" } 108 | 109 | it "should not yield anything" do 110 | pending " does not appear to ever have two or fewer children" 111 | 112 | expect { |b| 113 | subject.each_intermediate(&b) 114 | }.to_not yield_control 115 | end 116 | end 117 | end 118 | 119 | describe "#intermediates" do 120 | context "when there are more than two 'certificate' XML children" do 121 | let(:xpath) { "#{super()}[count(certificate) > 2]" } 122 | 123 | it "should yield the intermediate certificates" do 124 | expect(subject.intermediates).to be_a(Array).and(all(be_kind_of(SSLyze::XML::Certinfo::Certificate))) 125 | end 126 | end 127 | 128 | context "when there are two or fewer 'certificate' XML children" do 129 | let(:xpath) { "#{super()}[count(certificate) <= 2]" } 130 | 131 | it "should not yield anything" do 132 | pending " does not appear to ever have two or fewer children" 133 | 134 | expect(subject.intermediates).to be_empty 135 | end 136 | end 137 | end 138 | 139 | describe "#root" do 140 | context "when at least one 'certificate' XML child exists" do 141 | let(:xpath) { "#{super()}[certificate]" } 142 | 143 | it do 144 | expect(subject.root).to be_a(SSLyze::XML::Certinfo::Certificate) 145 | end 146 | 147 | it "should select the last 'certificate' XML child" do 148 | expect(subject.root).to be == \ 149 | SSLyze::XML::Certinfo::Certificate.new(xml.at("#{xpath}/certificate[last()]")) 150 | end 151 | end 152 | 153 | context "when no 'certificate' XML children exist" do 154 | let(:xpath) { "#{super()}[not(./certificate)]" } 155 | 156 | it do 157 | pending "need an example with no 'certificate' XML children" 158 | 159 | expect(subject.root).to be nil 160 | end 161 | end 162 | end 163 | end 164 | -------------------------------------------------------------------------------- /spec/xml/certinfo/certificate_validation_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/certinfo/certificate_validation' 4 | 5 | describe SSLyze::XML::Certinfo::CertificateValidation do 6 | include_examples "XML specs" 7 | 8 | subject do 9 | described_class.new(xml.at('/document/results/target/certinfo/certificateValidation')) 10 | end 11 | 12 | describe "#hostname_validation" do 13 | it do 14 | expect(subject.hostname_validation).to be_kind_of(described_class::HostnameValidation) 15 | end 16 | end 17 | 18 | describe "#each_path_validation" do 19 | it "should yield successive PathValidation objects" do 20 | expect { |b| 21 | subject.each_path_validation(&b) 22 | }.to yield_successive_args( 23 | described_class::PathValidation, 24 | described_class::PathValidation, 25 | described_class::PathValidation, 26 | described_class::PathValidation, 27 | described_class::PathValidation 28 | ) 29 | end 30 | end 31 | 32 | describe "#path_validations" do 33 | subject { super().path_validations } 34 | 35 | it do 36 | expect(subject).to_not be_empty 37 | expect(subject).to all(be_kind_of(described_class::PathValidation)) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/xml/certinfo/ocsp_stapling/ocsp_response_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/certinfo/ocsp_stapling/ocsp_response' 4 | 5 | describe SSLyze::XML::Certinfo::OCSPStapling::OCSPResponse do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target/certinfo/ocspStapling/ocspResponse' } 9 | 10 | subject { described_class.new(xml.at(xpath)) } 11 | 12 | describe "#is_trusted_by_mozilla_ca_store?" do 13 | it "should query @isTrustedByMozillaCAStore" do 14 | expect(subject.is_trusted_by_mozilla_ca_store?).to be true 15 | end 16 | end 17 | 18 | describe "#responder_id" do 19 | let(:expected_responder_id) { '5168FF90AF0207753CCCD9656462A212B859723B' } 20 | 21 | let(:xpath) { "#{super()}[responderID/text()='#{expected_responder_id}']" } 22 | 23 | it "should parse the 'responderID' XML element" do 24 | expect(subject.responder_id).to be == expected_responder_id 25 | end 26 | end 27 | 28 | describe "#status" do 29 | let(:xpath) { "#{super()}[@status='SUCCESSFUL']" } 30 | 31 | it "should parse the status attribute" do 32 | expect(subject.status).to be == :successful 33 | end 34 | end 35 | 36 | describe "#successful?" do 37 | context "when responseStatus is 'successful'" do 38 | let(:xpath) { "#{super()}[@status='SUCCESSFUL']" } 39 | 40 | specify { expect(subject.successful?).to be true } 41 | end 42 | 43 | context "when status is not :successful" do 44 | before do 45 | expect(subject).to receive(:status).and_return(:failed) 46 | end 47 | 48 | specify { expect(subject.successful?).to be false } 49 | end 50 | end 51 | 52 | describe "#produced_at" do 53 | let(:expected_time) { node.at_xpath('producedAt').inner_text } 54 | 55 | it "should query producedAt and return a Time object" do 56 | expect(subject.produced_at).to be == Time.parse(expected_time) 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/xml/certinfo/ocsp_stapling_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/certinfo' 4 | 5 | describe SSLyze::XML::Certinfo::OCSPStapling do 6 | include_examples "XML specs" 7 | 8 | subject do 9 | described_class.new(xml.at('/document/results/target/certinfo/ocspStapling')) 10 | end 11 | 12 | describe "#ocsp_response" do 13 | context "when the 'ocspResponse' element is present" do 14 | subject do 15 | described_class.new(xml.at('/document/results/target/certinfo/ocspStapling[ocspResponse]')) 16 | end 17 | 18 | it do 19 | expect(subject.ocsp_response).to be_kind_of(described_class::OCSPResponse) 20 | end 21 | end 22 | 23 | context "when the 'ocspResponse' element is missing" do 24 | subject do 25 | described_class.new(xml.at('/document/results/target/certinfo/ocspStapling[not(ocspResponse)]')) 26 | end 27 | 28 | it { expect(subject.ocsp_response).to be nil } 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/xml/certinfo/received_certificate_chain_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/certinfo/received_certificate_chain' 4 | 5 | describe SSLyze::XML::Certinfo::ReceivedCertificateChain do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target/certinfo/receivedCertificateChain' } 9 | 10 | subject do 11 | described_class.new(xml.at(xpath)) 12 | end 13 | 14 | describe "#each_certificate" do 15 | context "when 'certificate' XML children are present" do 16 | let(:xpath) { "#{super()}[certificate]" } 17 | let(:count) { node.xpath('./certificate').count } 18 | 19 | it "should yield successive Certificate objects" do 20 | expect { |b| 21 | subject.each_certificate(&b) 22 | }.to yield_successive_args( 23 | *[SSLyze::XML::Certinfo::Certificate] * count 24 | ) 25 | end 26 | end 27 | end 28 | 29 | describe "#certificates" do 30 | context "when 'certificate' XML children are present" do 31 | let(:xpath) { "#{super()}[certificate]" } 32 | 33 | it do 34 | expect(subject.certificates).to be_a(Array).and( 35 | all(be_kind_of(SSLyze::XML::Certinfo::Certificate)) 36 | ) 37 | end 38 | end 39 | end 40 | 41 | describe "#leaf" do 42 | context "when at least one 'certificate' XML child exists" do 43 | let(:xpath) { "#{super()}[certificate]" } 44 | 45 | it do 46 | expect(subject.leaf).to be_a(SSLyze::XML::Certinfo::Certificate) 47 | end 48 | 49 | it "should select the first 'certificate' XML child" do 50 | expect(subject.leaf).to be == \ 51 | SSLyze::XML::Certinfo::Certificate.new(xml.at("#{xpath}/certificate[1]")) 52 | end 53 | end 54 | 55 | context "when no 'certificate' XML children exist" do 56 | let(:xpath) { "#{super()}[not(./certificate)]" } 57 | 58 | it do 59 | pending "need an example with no 'certificate' XML children" 60 | 61 | expect(subject.leaf).to be nil 62 | end 63 | end 64 | end 65 | 66 | describe "#each_intermediate" do 67 | context "when there are more than two 'certificate' XML children" do 68 | let(:xpath) { "#{super()}[count(certificate) > 2]" } 69 | let(:xpath_count) { xml.at(xpath).xpath('certificate').count - 2 } 70 | 71 | it "should yield the intermediate certificates" do 72 | expect { |b| 73 | subject.each_intermediate(&b) 74 | }.to yield_successive_args( 75 | *Array.new(xpath_count,SSLyze::XML::Certinfo::Certificate) 76 | ) 77 | end 78 | end 79 | 80 | context "when there are two or fewer 'certificate' XML children" do 81 | let(:xpath) { "#{super()}[count(certificate) <= 2]" } 82 | 83 | it "should not yield anything" do 84 | expect { |b| 85 | subject.each_intermediate(&b) 86 | }.to_not yield_control 87 | end 88 | end 89 | end 90 | 91 | describe "#intermediates" do 92 | context "when there are more than two 'certificate' XML children" do 93 | let(:xpath) { "#{super()}[count(certificate) > 2]" } 94 | let(:xpath_count) { xml.at(xpath).xpath('certificate').count - 2 } 95 | 96 | it "should yield the intermediate certificates" do 97 | expect(subject.intermediates).to be_a(Array).and(all(be_kind_of(SSLyze::XML::Certinfo::Certificate))) 98 | end 99 | end 100 | 101 | context "when there are two or fewer 'certificate' XML children" do 102 | let(:xpath) { "#{super()}[count(certificate) <= 2]" } 103 | 104 | it "should not yield anything" do 105 | expect(subject.intermediates).to be_empty 106 | end 107 | end 108 | end 109 | 110 | describe "#root" do 111 | context "when at least one 'certificate' XML child exists" do 112 | let(:xpath) { "#{super()}[certificate]" } 113 | 114 | it do 115 | expect(subject.root).to be_a(SSLyze::XML::Certinfo::Certificate) 116 | end 117 | 118 | it "should select the last 'certificate' XML child" do 119 | expect(subject.root).to be == \ 120 | SSLyze::XML::Certinfo::Certificate.new(xml.at("#{xpath}/certificate[last()]")) 121 | end 122 | end 123 | 124 | context "when no 'certificate' XML children exist" do 125 | let(:xpath) { "#{super()}[not(./certificate)]" } 126 | 127 | it do 128 | pending "need an example with no 'certificate' XML children" 129 | 130 | expect(subject.root).to be nil 131 | end 132 | end 133 | end 134 | 135 | describe "#is_chain_order_valid?" do 136 | it "should return a Boolean value" do 137 | expect(subject.is_chain_order_valid?).to be(true).or(be(false)) 138 | end 139 | end 140 | 141 | describe "#contains_anchor_certificate?" do 142 | context "when the 'containsAnchorCertificate' XML attribute is present" do 143 | let(:xpath) { "#{super()}[@containsAnchorCertificate]" } 144 | 145 | it "should return a Boolean value" do 146 | expect(subject.contains_anchor_certificate?).to be(true).or(be(false)) 147 | end 148 | end 149 | 150 | context "when the 'containsAnchorCertificate' XML attribute is omitted" do 151 | let(:xpath) { "#{super()}[not(@containsAnchorCertificate)]" } 152 | 153 | it do 154 | pending "need an example where 'containsAnchorCertificate' is missing" 155 | 156 | expect(subject.contains_anchor_certificate?).to be nil 157 | end 158 | end 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /spec/xml/certinfo_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/certinfo' 4 | 5 | describe SSLyze::XML::Certinfo do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target/certinfo' } 9 | 10 | subject do 11 | described_class.new(xml.at(xpath)) 12 | end 13 | 14 | describe "#received_certificate_chain" do 15 | it "should return a ReceivedCertificateChain object" do 16 | expect(subject.received_certificate_chain).to be_a(described_class::ReceivedCertificateChain) 17 | end 18 | end 19 | 20 | describe "#certificate_validation" do 21 | subject do 22 | described_class.new(xml.at("#{xpath}[receivedCertificateChain]")) 23 | end 24 | 25 | it "should return a CertificateValidation element" do 26 | expect(subject.certificate_validation).to be_kind_of(described_class::CertificateValidation) 27 | end 28 | end 29 | 30 | describe "#verified_certificate_chain" do 31 | it "should return the #verified_certificate_chain from within one of the #certificate_validation.path_validations" do 32 | expect(subject.verified_certificate_chain).to be_kind_of(described_class::CertificateValidation::VerifiedCertificateChain) 33 | end 34 | end 35 | 36 | describe "#ocsp_stapling" do 37 | subject do 38 | described_class.new(xml.at("#{xpath}[ocspStapling]")) 39 | end 40 | 41 | it "should return a OCSPStapling object" do 42 | expect(subject.ocsp_stapling).to be_kind_of(described_class::OCSPStapling) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/xml/compression/compression_method_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/compression/compression_method' 4 | 5 | describe SSLyze::XML::Compression::CompressionMethod do 6 | include_examples "XML specs" 7 | 8 | subject do 9 | described_class.new(xml.at('/document/results/target/compression/compressionMethod')) 10 | end 11 | 12 | describe "#is_supported??" do 13 | it "should prase the 'isSupported' attribute" do 14 | expect(subject.is_supported?).to be(true).or(be(false)) 15 | end 16 | end 17 | 18 | describe "#type" do 19 | it "should parse the 'type' attribute" do 20 | expect(subject.type).to be :DEFLATE 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/xml/compression_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'xml/plugin_examples' 4 | require 'sslyze/xml/compression' 5 | 6 | describe SSLyze::XML::Compression do 7 | include_examples "XML specs" 8 | include_examples "Plugin element" 9 | 10 | let(:xpath) { '/document/results/target/compression' } 11 | 12 | subject { described_class.new(xml.at(xpath)) } 13 | 14 | describe "#deflate" do 15 | it do 16 | expect(subject.deflate).to be_kind_of(described_class::CompressionMethod) 17 | end 18 | 19 | it "should parse the DEFLATE compressionMethod" do 20 | expect(subject.deflate.type).to be :DEFLATE 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/xml/heartbleed/openssl_heartbleed_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/heartbleed/openssl_heartbleed' 4 | 5 | describe SSLyze::XML::Heartbleed::OpenSSLHeartbleed do 6 | include_examples "XML specs" 7 | 8 | subject do 9 | described_class.new(xml.at('/document/results/target/heartbleed/openSslHeartbleed')) 10 | end 11 | 12 | describe "#is_vulnerable?" do 13 | it "should prase the 'isVulnerable' attribute" do 14 | expect(subject.is_vulnerable?).to be(true).or(be(false)) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/xml/heartbleed_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'xml/plugin_examples' 4 | require 'sslyze/xml/heartbleed' 5 | 6 | describe SSLyze::XML::Heartbleed do 7 | include_examples "XML specs" 8 | include_examples "Plugin element" 9 | 10 | let(:xpath) { '/document/results/target/heartbleed' } 11 | 12 | subject { described_class.new(xml.at(xpath)) } 13 | 14 | describe "#openssl_heartbleed" do 15 | context "when the '' XML element is present" do 16 | subject do 17 | described_class.new(xml.at("#{xpath}[openSslHeartbleed]")) 18 | end 19 | 20 | it do 21 | expect(subject.openssl_heartbleed).to be_kind_of(described_class::OpenSSLHeartbleed) 22 | end 23 | end 24 | 25 | context "when the '' XML element is missing" do 26 | subject do 27 | described_class.new(xml.at("#{xpath}[not(./openSslHeartbleed)]")) 28 | end 29 | 30 | it do 31 | pending "need to find a host where is omitted" 32 | 33 | expect(subject.openssl_heartbleed).to be nil 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/xml/http_headers/http_public_key_pinning_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/http_headers/http_public_key_pinning' 4 | 5 | describe SSLyze::XML::HTTPHeaders::HTTPPublicKeyPinning do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target/http_headers/httpPublicKeyPinning' } 9 | 10 | subject do 11 | described_class.new(xml.at(xpath)) 12 | end 13 | 14 | describe "#include_sub_domains?" do 15 | context "when the 'includeSubDomains' attribute is present" do 16 | let(:xpath) { "#{super()}[@includeSubDomains]" } 17 | 18 | context "and it is 'True'" do 19 | let(:xpath) { "#{super()}[@includeSubDomains='True']" } 20 | 21 | it do 22 | pending "find domain where includeSubDomains='True'" 23 | 24 | expect(subject.include_sub_domains?).to be(true) 25 | end 26 | end 27 | 28 | context "but it is 'False'" do 29 | let(:xpath) { "#{super()}[@includeSubDomains='False']" } 30 | 31 | it do 32 | expect(subject.include_sub_domains?).to be(false) 33 | end 34 | end 35 | end 36 | 37 | context "when the 'includeSubDomains' attribute is not present" do 38 | let(:xpath) { "#{super()}[not(@includeSubDomains)]" } 39 | 40 | it { expect(subject.include_sub_domains?).to be nil } 41 | end 42 | end 43 | 44 | describe "#is_supported?" do 45 | context "when the 'isSupported' attribute is 'True'" do 46 | let(:xpath) { "#{super()}[@isSupported='True']" } 47 | 48 | it { expect(subject.is_supported?).to be true } 49 | end 50 | 51 | context "when the 'isSupported' attribute is 'False'" do 52 | let(:xpath) { "#{super()}[@isSupported='False']" } 53 | 54 | it { expect(subject.is_supported?).to be false } 55 | end 56 | end 57 | 58 | describe "#max_age" do 59 | context "when the 'maxAge' attribute is present" do 60 | let(:xpath) { "#{super()}[@maxAge]" } 61 | 62 | it "should return the 'maxAge' attribute" do 63 | expect(subject.max_age).to be > 0 64 | end 65 | end 66 | 67 | context "when the 'maxAge' attribute is not present" do 68 | let(:xpath) { "#{super()}[not(@maxAge)]" } 69 | 70 | it { expect(subject.max_age).to be nil } 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/xml/http_headers/http_strict_transport_security_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/http_headers/http_strict_transport_security' 4 | 5 | describe SSLyze::XML::HTTPHeaders::HTTPStrictTransportSecurity do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target/http_headers/httpStrictTransportSecurity' } 9 | 10 | subject do 11 | described_class.new(xml.at(xpath)) 12 | end 13 | 14 | describe "#include_sub_domains?" do 15 | context "when the 'includeSubDomains' attribute is present" do 16 | subject do 17 | described_class.new(xml.at("#{xpath}[@includeSubDomains]")) 18 | end 19 | 20 | it "should return a Boolean value" do 21 | expect(subject.include_sub_domains?).to be(true).or(be(false)) 22 | end 23 | end 24 | 25 | context "when the 'includeSubDomains' attribute is not present" do 26 | subject do 27 | described_class.new(xml.at("#{xpath}[not(@includeSubDomains)]")) 28 | end 29 | 30 | it do 31 | expect(subject.include_sub_domains?).to be nil 32 | end 33 | end 34 | end 35 | 36 | describe "#is_supported?" do 37 | context "when the 'isSupported' attribute is 'True'" do 38 | subject do 39 | described_class.new(xml.at("#{xpath}[@isSupported=\"True\"]")) 40 | end 41 | 42 | it { expect(subject.is_supported?).to be true } 43 | end 44 | 45 | context "when the 'isSupported' attribute is 'False'" do 46 | subject do 47 | described_class.new(xml.at("#{xpath}[@isSupported=\"False\"]")) 48 | end 49 | 50 | it do 51 | expect(subject.is_supported?).to be false 52 | end 53 | end 54 | end 55 | 56 | describe "#max_age" do 57 | context "when the 'maxAge' attribute is present" do 58 | subject do 59 | described_class.new(xml.at("#{xpath}[@maxAge]")) 60 | end 61 | 62 | it "should return the 'maxAge' attribute" do 63 | expect(subject.max_age).to be > 0 64 | end 65 | end 66 | 67 | context "when the 'maxAge' attribute is not present" do 68 | subject do 69 | described_class.new(xml.at("#{xpath}[not(@maxAge)]")) 70 | end 71 | 72 | it do 73 | expect(subject.max_age).to be nil 74 | end 75 | end 76 | end 77 | 78 | describe "#preload?" do 79 | context "when the 'preload' attribute is present" do 80 | subject do 81 | described_class.new(xml.at("#{xpath}[@preload]")) 82 | end 83 | 84 | it "should return a Boolean value" do 85 | expect(subject.preload?).to be(true).or(be(false)) 86 | end 87 | end 88 | 89 | context "when the 'preload' attribute is not present" do 90 | subject do 91 | described_class.new(xml.at("#{xpath}[not(@preload)]")) 92 | end 93 | 94 | it do 95 | expect(subject.preload?).to be nil 96 | end 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /spec/xml/http_headers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'xml/plugin_examples' 4 | require 'sslyze/xml/http_headers' 5 | 6 | describe SSLyze::XML::HTTPHeaders do 7 | include_examples "XML specs" 8 | include_examples "Plugin element" 9 | 10 | let(:xpath) { '/document/results/target/http_headers' } 11 | 12 | subject { described_class.new(xml.at(xpath)) } 13 | 14 | describe "#http_strict_transport_security" do 15 | context "when the '' XML element is present" do 16 | subject do 17 | described_class.new(xml.at("#{xpath}[httpStrictTransportSecurity]")) 18 | end 19 | 20 | it do 21 | expect(subject.http_strict_transport_security).to \ 22 | be_kind_of(described_class::HTTPStrictTransportSecurity) 23 | end 24 | end 25 | 26 | context "when the '' XML element is omitted" do 27 | subject do 28 | described_class.new(xml.at("#{xpath}[not(./httpStrictTransportSecurity)]")) 29 | end 30 | 31 | it do 32 | pending "need an example with no ''" 33 | 34 | expect(subject.http_strict_transport_security).to be nil 35 | end 36 | end 37 | end 38 | 39 | describe "#http_public_key_pinning" do 40 | context "when the '' XML element is present" do 41 | subject do 42 | described_class.new(xml.at("#{xpath}[httpPublicKeyPinning]")) 43 | end 44 | 45 | it do 46 | expect(subject.http_public_key_pinning).to \ 47 | be_kind_of(described_class::HTTPPublicKeyPinning) 48 | end 49 | end 50 | 51 | context "when the '' XML element is omitted" do 52 | subject do 53 | described_class.new(xml.at("#{xpath}[not(./httpPublicKeyPinning)]")) 54 | end 55 | 56 | it do 57 | pending "need an example with no ''" 58 | 59 | expect(subject.http_public_key_pinning).to be nil 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/xml/invalid_target_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/invalid_target' 4 | 5 | describe SSLyze::XML::InvalidTarget do 6 | include_examples "XML specs" 7 | 8 | let(:host) { 'foo' } 9 | let(:port) { 443 } 10 | let(:target) { "#{host}:#{port}" } 11 | let(:xpath) { "/document/invalidTargets/invalidTarget[text()='#{target}']" } 12 | 13 | subject { described_class.new(xml.at(xpath)) } 14 | 15 | describe "#target" do 16 | it "must return the target text" do 17 | expect(subject.target).to be == target 18 | end 19 | end 20 | 21 | describe "#host" do 22 | it "must return the host component of the target" do 23 | expect(subject.host).to be == host 24 | end 25 | end 26 | 27 | describe "#port" do 28 | it "must return the port component of the target" do 29 | expect(subject.port).to be port 30 | end 31 | end 32 | 33 | describe "#error" do 34 | it "must parse the ip attribute" do 35 | expect(subject.error).to be == 'Could not resolve hostname' 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/xml/plugin_examples.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | 3 | shared_examples_for "Plugin element" do 4 | describe "#title" do 5 | it "should parse the title attribute" do 6 | expect(subject.title).to be_kind_of(String) 7 | expect(subject.title).not_to be_empty 8 | end 9 | end 10 | 11 | describe "#exception" do 12 | pending "need an examples of plugin elements with exception messages" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/xml/protocol/cipher_suite/key_exchange_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/protocol/cipher_suite/key_exchange' 4 | 5 | describe SSLyze::XML::Protocol::CipherSuite::KeyExchange do 6 | include_examples "XML specs" 7 | 8 | subject { described_class.new(xml.at('/document/results/target/tlsv1_2/preferredCipherSuite/cipherSuite/keyExchange')) } 9 | 10 | describe "#a" do 11 | it "should parse the A attribute" do 12 | expect(subject.a).to be == '0x00ffffffff00000001000000000000000000000000fffffffffffffffffffffffc' 13 | end 14 | end 15 | 16 | describe "#b" do 17 | it "should parse the B attribute" do 18 | expect(subject.b).to be == '0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b' 19 | end 20 | end 21 | 22 | describe "#cofactor" do 23 | it "should parse the Cofactor attribute" do 24 | expect(subject.cofactor).to be == 1 25 | end 26 | end 27 | 28 | describe "#field_type" do 29 | it "should parse the Field_Type attribute" do 30 | expect(subject.field_type).to be == 'prime-field' 31 | end 32 | end 33 | 34 | describe "#generator" do 35 | it "should parse the Generator attribute" do 36 | expect(subject.generator).to be == '0x046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5' 37 | end 38 | end 39 | 40 | describe "#generator_type" do 41 | it "should parse the GeneratorType attribute" do 42 | expect(subject.generator_type).to be == :uncompressed 43 | end 44 | end 45 | 46 | describe "#group_size" do 47 | it "should parse the GroupSize attribute" do 48 | expect(subject.group_size).to be == 256 49 | end 50 | end 51 | 52 | describe "#order" do 53 | it "should parse the Order attribute" do 54 | expect(subject.order).to be == '0x00ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551' 55 | end 56 | end 57 | 58 | describe "#prime" do 59 | it "should parse the Prime attribute" do 60 | expect(subject.prime).to be == '0x00ffffffff00000001000000000000000000000000ffffffffffffffffffffffff' 61 | end 62 | end 63 | 64 | describe "#seed" do 65 | it "should parse the Seed attribute" do 66 | expect(subject.seed).to be == '0xc49d360886e704936a6678e1139d26b7819f7e90' 67 | end 68 | end 69 | 70 | describe "#type" do 71 | it "should parse the Type attribute" do 72 | expect(subject.type).to be == :ECDH 73 | end 74 | end 75 | 76 | describe "#dh?" do 77 | context "when #type is :DH" do 78 | before { expect(subject).to receive(:type).and_return(:DH) } 79 | 80 | it { expect(subject.dh?).to be(true) } 81 | end 82 | 83 | context "when #type is :ECDHE " do 84 | before { expect(subject).to receive(:type).and_return(:ECDHE) } 85 | 86 | it { expect(subject.dh?).to be(false) } 87 | end 88 | end 89 | 90 | describe "#ecdhe?" do 91 | context "when #type is :DH" do 92 | before { expect(subject).to receive(:type).and_return(:DH) } 93 | 94 | it { expect(subject.ecdhe?).to be(false) } 95 | end 96 | 97 | context "when #type is :ECDHE " do 98 | before { expect(subject).to receive(:type).and_return(:ECDHE) } 99 | 100 | it { expect(subject.ecdhe?).to be(true) } 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /spec/xml/protocol/cipher_suite_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/protocol/cipher_suite' 4 | 5 | describe SSLyze::XML::Protocol::CipherSuite do 6 | include_examples "XML specs" 7 | 8 | let(:expected_name) { 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' } 9 | 10 | let(:xpath) { '/document/results/target/tlsv1_2/preferredCipherSuite/cipherSuite' } 11 | 12 | describe "#name" do 13 | let(:xpath) { "#{super()}[@name='#{expected_name}']" } 14 | 15 | it "should parse the name attribute" do 16 | expect(subject.name).to be == expected_name 17 | end 18 | end 19 | 20 | describe "#rfc_name" do 21 | let(:xpath) { "#{super()}[@name='#{expected_name}']" } 22 | 23 | it "should return the RFC cipher suite name" do 24 | expect(subject.rfc_name).to be == expected_name 25 | end 26 | end 27 | 28 | describe "#openssl_name" do 29 | let(:openssl_name) { 'ECDHE-RSA-AES128-GCM-SHA256' } 30 | let(:xpath) { "#{super()}[@name='#{expected_name}']" } 31 | 32 | it "should map the RFC name back to the OpenSSL name" do 33 | expect(subject.openssl_name).to be == openssl_name 34 | end 35 | end 36 | 37 | describe "#connection_status" do 38 | it "should parse the connectionStatus attribute" do 39 | expect(subject.connection_status).to be == node['connectionStatus'] 40 | end 41 | end 42 | 43 | describe "#anonymous?" do 44 | it "should query the anonymous attribute" do 45 | expect(subject.anonymous?).to be false 46 | end 47 | end 48 | 49 | describe "#key_size" do 50 | it "should return the keySize attribute" do 51 | expect(subject.key_size).to be 128 52 | end 53 | end 54 | 55 | describe "#key_exchange" do 56 | let(:xpath) { '/document/results/target/tlsv1_2/acceptedCipherSuites/cipherSuite' } 57 | 58 | context "when the keyExchange child is present" do 59 | let(:xpath) { "#{super()}[keyExchange]" } 60 | 61 | it do 62 | expect(subject.key_exchange).to be_kind_of(described_class::KeyExchange) 63 | end 64 | end 65 | 66 | context "when the keyExchange object is missing" do 67 | let(:xpath) { "#{super()}[not(./keyExchange)]" } 68 | 69 | it { expect(subject.key_exchange).to be nil } 70 | end 71 | end 72 | 73 | end 74 | -------------------------------------------------------------------------------- /spec/xml/protocol_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/protocol' 4 | 5 | describe SSLyze::XML::Protocol do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target/tlsv1_2' } 9 | 10 | subject { described_class.new(xml.at(xpath)) } 11 | 12 | describe "#name" do 13 | it "should return the protocol name" do 14 | expect(subject.name).to be == :tlsv1_2 15 | end 16 | end 17 | 18 | describe "#title" do 19 | it "must parse the title attribute" do 20 | expect(subject.title).to be == 'TLSV1_2 Cipher Suites' 21 | end 22 | end 23 | 24 | describe "#preferred_cipher_suite" do 25 | context "when the 'preferredCipherSuite' XML element has children" do 26 | let(:xpath) do 27 | '/document/results/target/*[preferredCipherSuite/cipherSuite]' 28 | end 29 | 30 | it do 31 | expect(subject.preferred_cipher_suite).to be_kind_of(described_class::CipherSuite) 32 | end 33 | end 34 | 35 | context "when the preferredCipherSuite' XML element has no children" do 36 | let(:xpath) do 37 | '/document/results/target/*[preferredCipherSuite][not(preferredCipherSuite/cipherSuite)]' 38 | end 39 | 40 | it do 41 | expect(subject.preferred_cipher_suite).to be nil 42 | end 43 | end 44 | end 45 | 46 | describe "#each_accepted_cipher_suite" do 47 | it "should yield CipherSuite objects" do 48 | expect { |b| 49 | subject.each_accepted_cipher_suite(&b) 50 | } 51 | end 52 | 53 | context "when given no block" do 54 | it { expect(subject.each_accepted_cipher_suite).to be_an(Enumerator) } 55 | end 56 | end 57 | 58 | describe "#accepted_cipher_suites" do 59 | it "should return an Array of CipherSuites" do 60 | expect(subject.accepted_cipher_suites).to be_an(Array).and( 61 | all(be_a(described_class::CipherSuite)) 62 | ) 63 | end 64 | end 65 | 66 | describe "#each_rejected_cipher_suite" do 67 | it "should yield CipherSuite objects" do 68 | expect { |b| 69 | subject.each_rejected_cipher_suite(&b) 70 | } 71 | end 72 | 73 | context "when given no block" do 74 | it { expect(subject.each_rejected_cipher_suite).to be_an(Enumerator) } 75 | end 76 | end 77 | 78 | describe "#rejected_cipher_suites" do 79 | it "should return an Array of CipherSuites" do 80 | expect(subject.rejected_cipher_suites).to be_an(Array).and( 81 | all(be_a(described_class::CipherSuite)) 82 | ) 83 | end 84 | end 85 | 86 | describe "#supported?" do 87 | context "when there are preferred cipher suites" do 88 | it "should return true" do 89 | expect(subject.supported?).to be(true) 90 | end 91 | end 92 | end 93 | 94 | describe "#each_error" do 95 | it "should yield CipherSuite objects" do 96 | pending "need a target with non-empty ''" 97 | 98 | expect { |b| 99 | subject.each_error(&b) 100 | }.to yield_successive_args(described_class::CipherSuite) 101 | end 102 | 103 | context "when given no block" do 104 | it { expect(subject.each_error).to be_an(Enumerator) } 105 | end 106 | end 107 | 108 | describe "#errors" do 109 | it "should return an Array of CipherSuites" do 110 | expect(subject.errors).to be_an(Array).and( 111 | all(be_a(described_class::CipherSuite)) 112 | ) 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /spec/xml/reneg/session_renegotiation_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/reneg/session_renegotiation' 4 | 5 | describe SSLyze::XML::Reneg::SessionRenegotiation do 6 | include_examples "XML specs" 7 | 8 | subject do 9 | described_class.new(xml.at('/document/results/target/reneg/sessionRenegotiation')) 10 | end 11 | 12 | describe "#can_be_client_initiated?" do 13 | it "should return the 'canBeClientInitiated' attribute" do 14 | expect(subject.can_be_client_initiated?).to be(true).or(be(false)) 15 | end 16 | end 17 | 18 | describe "#is_secure?" do 19 | it "should return the 'isSecure' attribute" do 20 | expect(subject.is_secure?).to be(true).or(be(false)) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/xml/reneg_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'xml/plugin_examples' 4 | require 'sslyze/xml/reneg' 5 | 6 | describe SSLyze::XML::Reneg do 7 | include_examples "XML specs" 8 | include_examples "Plugin element" 9 | 10 | let(:xpath) { '/document/results/target/reneg' } 11 | 12 | subject { described_class.new(xml.at(xpath)) } 13 | 14 | describe "#session_renegotiation" do 15 | context "when the '' element is present" do 16 | subject do 17 | described_class.new(xml.at("#{xpath}[sessionRenegotiation]")) 18 | end 19 | 20 | it do 21 | expect(subject.session_renegotiation).to be_kind_of(described_class::SessionRenegotiation) 22 | end 23 | end 24 | 25 | context "when the '' element is omitted" do 26 | subject do 27 | described_class.new(xml.at("#{xpath}[not(./sessionRenegotiation)]")) 28 | end 29 | 30 | it do 31 | expect(subject.session_renegotiation).to be nil 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/xml/resum/session_resumption_with_session_ids_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/resum/session_resumption_with_session_ids' 4 | 5 | describe SSLyze::XML::Resum::SessionResumptionWithSessionIDs do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target/resum/sessionResumptionWithSessionIDs' } 9 | 10 | subject do 11 | described_class.new(xml.at(xpath)) 12 | end 13 | 14 | describe "#failed_attempts" do 15 | it "should return the failedAttempts attribute" do 16 | expect(subject.failed_attempts).to be_between(0, 5) 17 | end 18 | end 19 | 20 | describe "#is_supported?" do 21 | it "should parse the isSupported attribute" do 22 | expect(subject.is_supported?).to be(true).or(be(false)) 23 | end 24 | end 25 | 26 | describe "#successful_attempts" do 27 | it "should return the successfulAttempts attribute" do 28 | expect(subject.successful_attempts).to be_between(0, 5) 29 | end 30 | end 31 | 32 | describe "#total_attempts" do 33 | it "should return the totalAttempts attribute" do 34 | expect(subject.total_attempts).to be 5 35 | end 36 | end 37 | 38 | describe "#error_count" do 39 | it "should return the errors attribute" do 40 | expect(subject.error_count).to be 0 41 | end 42 | end 43 | 44 | describe "#each_error" do 45 | context "when a block is given" do 46 | context "and there are 'error' child XML elements" do 47 | subject do 48 | described_class.new(xml.at("#{xpath}[./error]")) 49 | end 50 | 51 | it "should yield the successive error messages" do 52 | pending "need an example with 'error' XML elements" 53 | 54 | expect { |b| 55 | subject.each_error(&b) 56 | }.to yield_successive_args("timeout - timed out") 57 | end 58 | end 59 | 60 | context "but there are no 'error' child XML elements" do 61 | subject do 62 | described_class.new(xml.at("#{xpath}[not(./error)]")) 63 | end 64 | 65 | it do 66 | expect { |b| 67 | subject.each_error(&b) 68 | }.to_not yield_control 69 | end 70 | end 71 | end 72 | 73 | context "when no block is given" do 74 | it "should return an Enumerator" do 75 | expect(subject.each_error).to be_kind_of(Enumerator) 76 | end 77 | end 78 | end 79 | 80 | describe "#errors" do 81 | context "when there are 'error' child XML elements" do 82 | subject do 83 | described_class.new(xml.at("#{xpath}[./error]")) 84 | end 85 | 86 | it "should return the error messages" do 87 | pending "need an example with 'error' XML elements" 88 | 89 | expect(subject.errors).to be == ["timeout - timed out"] 90 | end 91 | end 92 | 93 | context "when there are no 'error' child XML elements" do 94 | subject do 95 | described_class.new(xml.at("#{xpath}[not(./error)]")) 96 | end 97 | 98 | it do 99 | expect(subject.errors).to be == [] 100 | end 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /spec/xml/resum/session_resumption_with_tls_tickets_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/resum/session_resumption_with_tls_tickets' 4 | 5 | describe SSLyze::XML::Resum::SessionResumptionWithTLSTickets do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target/resum/sessionResumptionWithTLSTickets' } 9 | 10 | subject do 11 | described_class.new(xml.at(xpath)) 12 | end 13 | 14 | describe "#error" do 15 | context "when there is an 'error' attribute" do 16 | subject do 17 | described_class.new(xml.at("#{xpath}[@error]")) 18 | end 19 | 20 | it "should return the 'error' attribute" do 21 | pending "need an example with an 'error' attribute" 22 | 23 | expect(subject.error).to_not be_nil 24 | end 25 | end 26 | 27 | context "when there is no 'error' attribute" do 28 | subject do 29 | described_class.new(xml.at("#{xpath}[not(@error)]")) 30 | end 31 | 32 | it { expect(subject.error).to be nil } 33 | end 34 | end 35 | 36 | describe "#error?" do 37 | context "when there is an 'error' attribute" do 38 | subject do 39 | described_class.new(xml.at("#{xpath}[@error]")) 40 | end 41 | 42 | it "should return the 'error' attribute" do 43 | pending "need an example with an 'error' attribute" 44 | 45 | expect(subject.error?).to be true 46 | end 47 | end 48 | 49 | context "when there is no 'error' attribute" do 50 | subject do 51 | described_class.new(xml.at("#{xpath}[not(@error)]")) 52 | end 53 | 54 | it { expect(subject.error?).to be false } 55 | end 56 | end 57 | 58 | describe "#is_supported?" do 59 | context "when there is an 'isSupported' attribute" do 60 | subject do 61 | described_class.new(xml.at("#{xpath}[@isSupported]")) 62 | end 63 | 64 | it "should return the 'isSupported' attribute" do 65 | expect(subject.is_supported?).to be(true).or(be(false)) 66 | end 67 | end 68 | 69 | context "when there is no 'isSupported' attribute" do 70 | subject do 71 | described_class.new(xml.at("#{xpath}[not(@isSupported)]")) 72 | end 73 | 74 | it do 75 | pending "need an example with no 'isSupported' attribute" 76 | 77 | expect(subject.is_supported?).to be false 78 | end 79 | end 80 | end 81 | 82 | describe "#reason" do 83 | context "when there is a 'reason' attribute" do 84 | subject do 85 | described_class.new(xml.at("#{xpath}[@reason]")) 86 | end 87 | 88 | it " should return the 'reason' attribute" do 89 | expect(subject.reason).to be == "TLS ticket not assigned" 90 | end 91 | end 92 | 93 | context "when there is no 'reason' attribute" do 94 | subject do 95 | described_class.new(xml.at("#{xpath}[not(@reason)]")) 96 | end 97 | 98 | it { expect(subject.reason).to be nil } 99 | end 100 | end 101 | 102 | describe "#to_s" do 103 | context "when there is a 'reason' attribute" do 104 | subject do 105 | described_class.new(xml.at("#{xpath}[@reason]")) 106 | end 107 | 108 | it "should return the reason" do 109 | expect(subject.to_s).to be == subject.reason 110 | end 111 | end 112 | 113 | context "when there is no 'reason' attribute" do 114 | subject do 115 | described_class.new(xml.at("#{xpath}[not(@reason)]")) 116 | end 117 | 118 | it { expect(subject.to_s).to be == '' } 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /spec/xml/resum_rate_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'xml/plugin_examples' 4 | require 'sslyze/xml/resum_rate' 5 | 6 | describe SSLyze::XML::ResumRate do 7 | include_examples "XML specs" 8 | include_examples "Plugin element" 9 | 10 | let(:xpath) { '/document/results/target/resum_rate' } 11 | 12 | subject { described_class.new(xml.at(xpath)) } 13 | 14 | describe "#session_resumption_with_session_ids" do 15 | context "when the XML element is present" do 16 | subject do 17 | described_class.new(xml.at("#{xpath}[sessionResumptionWithSessionIDs]")) 18 | end 19 | 20 | it do 21 | expect(subject.session_resumption_with_session_ids).to \ 22 | be_kind_of(described_class::SessionResumptionWithSessionIDs) 23 | end 24 | end 25 | 26 | context "when the XML element is omitted" do 27 | pending "need an example with no " 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/xml/resum_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'xml/plugin_examples' 4 | require 'sslyze/xml/resum' 5 | 6 | describe SSLyze::XML::Resum do 7 | include_examples "XML specs" 8 | include_examples "Plugin element" 9 | 10 | let(:xpath) { '/document/results/target/resum' } 11 | 12 | subject { described_class.new(xml.at(xpath)) } 13 | 14 | describe "#session_resumption_with_session_ids" do 15 | context "when the XML element is present" do 16 | subject do 17 | described_class.new(xml.at("#{xpath}[sessionResumptionWithSessionIDs]")) 18 | end 19 | 20 | it do 21 | expect(subject.session_resumption_with_session_ids).to \ 22 | be_kind_of(described_class::SessionResumptionWithSessionIDs) 23 | end 24 | end 25 | 26 | context "when the XML element is omitted" do 27 | pending "need an example with no " 28 | end 29 | end 30 | 31 | describe "#session_resumption_with_tls_tickets" do 32 | context "when the XML element is present" do 33 | subject do 34 | described_class.new(xml.at("#{xpath}[sessionResumptionWithTLSTickets]")) 35 | end 36 | 37 | it do 38 | expect(subject.session_resumption_with_tls_tickets).to \ 39 | be_kind_of(described_class::SessionResumptionWithTLSTickets) 40 | end 41 | end 42 | 43 | context "when the XML element is omitted" do 44 | pending "need an example with no " 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/xml/target_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml/target' 4 | 5 | describe SSLyze::XML::Target do 6 | include_examples "XML specs" 7 | 8 | let(:xpath) { '/document/results/target' } 9 | let(:expected_ip) { node['ip'] } 10 | 11 | subject { described_class.new(xml.at(xpath)) } 12 | 13 | describe "#host" do 14 | let(:expected_host) { 'twitter.com' } 15 | 16 | let(:xpath) { "#{super()}[@host='#{expected_host}']" } 17 | 18 | it "must parse the host attribute" do 19 | expect(subject.host).to be == expected_host 20 | end 21 | end 22 | 23 | describe "#ip" do 24 | let(:xpath) { "#{super()}[@ip='#{expected_ip}']" } 25 | 26 | it "must parse the ip attribute" do 27 | expect(subject.ip).to be == expected_ip 28 | end 29 | end 30 | 31 | describe "#ipaddr" do 32 | it "must parse the ip attribute" do 33 | expect(subject.ipaddr).to be == IPAddr.new(expected_ip) 34 | end 35 | end 36 | 37 | describe "#port" do 38 | it "must parse the port attribute" do 39 | expect(subject.port).to be == 443 40 | end 41 | end 42 | 43 | describe "#certinfo" do 44 | it do 45 | expect(subject.certinfo).to be_kind_of(SSLyze::XML::Certinfo) 46 | end 47 | end 48 | 49 | describe "#compression" do 50 | context "when the 'compression' XML element is present" do 51 | subject do 52 | described_class.new(xml.at("#{xpath}[compression]")) 53 | end 54 | 55 | it do 56 | expect(subject.compression).to be_kind_of(SSLyze::XML::Compression) 57 | end 58 | end 59 | 60 | context "when the 'compression' XML element is missing" do 61 | subject do 62 | described_class.new(xml.at("#{xpath}[not(compression)]")) 63 | end 64 | 65 | it do 66 | pending "need an example where 'compression' is missing" 67 | 68 | expect(subject.compression).to be nil 69 | end 70 | end 71 | end 72 | 73 | describe "#heartbleed" do 74 | context "when the 'heartbleed' XML element is present" do 75 | subject do 76 | described_class.new(xml.at("#{xpath}[heartbleed]")) 77 | end 78 | 79 | it do 80 | expect(subject.heartbleed).to be_kind_of(SSLyze::XML::Heartbleed) 81 | end 82 | end 83 | 84 | context "when the 'heartbleed' XML element is missing" do 85 | subject do 86 | described_class.new(xml.at("#{xpath}[not(heartbleed)]")) 87 | end 88 | 89 | it do 90 | pending "need an example without the 'heartbleed' XML element" 91 | 92 | expect(subject.heartbleed).to be nil 93 | end 94 | end 95 | end 96 | 97 | describe "#reneg" do 98 | it "should return a Reneg object" do 99 | expect(subject.reneg).to be_kind_of(SSLyze::XML::Reneg) 100 | end 101 | end 102 | 103 | describe "#sslv2" do 104 | it "must return a Protocol object" do 105 | expect(subject.sslv2).to be_kind_of(XML::Protocol) 106 | end 107 | end 108 | 109 | describe "#sslv3" do 110 | it "must return a Protocol object" do 111 | expect(subject.sslv3).to be_kind_of(XML::Protocol) 112 | end 113 | end 114 | 115 | describe "#tlsv1" do 116 | it "must return a Protocol object" do 117 | expect(subject.tlsv1).to be_kind_of(XML::Protocol) 118 | end 119 | end 120 | 121 | describe "#tlsv1_1" do 122 | it "must return a Protocol object" do 123 | expect(subject.tlsv1_1).to be_kind_of(XML::Protocol) 124 | end 125 | end 126 | 127 | describe "#tlsv1_2" do 128 | it "must return a Protocol object" do 129 | expect(subject.tlsv1_2).to be_kind_of(XML::Protocol) 130 | end 131 | end 132 | 133 | describe "#each_ssl_protocol" do 134 | context "when a block is given" do 135 | it "should yield sslv2 and sslv3" do 136 | expect { |b| 137 | subject.each_ssl_protocol(&b) 138 | }.to yield_successive_args(subject.sslv2, subject.sslv3) 139 | end 140 | end 141 | 142 | context "when no block is given" do 143 | it "should return an Enumerator" do 144 | expect(subject.each_ssl_protocol).to be_kind_of(Enumerator) 145 | end 146 | end 147 | end 148 | 149 | describe "#ssl_protocols" do 150 | it "should return sslv2 and sslv3" do 151 | expect(subject.ssl_protocols).to be == [subject.sslv2, subject.sslv3] 152 | end 153 | end 154 | 155 | describe "#each_tls_protocol" do 156 | context "when a block is given" do 157 | it "should yield tlsv1, tlsv1_1 and tlsv1_2" do 158 | expect { |b| 159 | subject.each_tls_protocol(&b) 160 | }.to yield_successive_args( 161 | subject.tlsv1, 162 | subject.tlsv1_1, 163 | subject.tlsv1_2 164 | ) 165 | end 166 | end 167 | 168 | context "when no block is given" do 169 | it "should return an Enumerator" do 170 | expect(subject.each_tls_protocol).to be_kind_of(Enumerator) 171 | end 172 | end 173 | end 174 | 175 | describe "#tls_protocols" do 176 | it "should return tlsv1, tlsv1_1 and tlsv1_2" do 177 | expect(subject.tls_protocols).to be == [ 178 | subject.tlsv1, 179 | subject.tlsv1_1, 180 | subject.tlsv1_2 181 | ] 182 | end 183 | end 184 | 185 | describe "#each_protocol" do 186 | context "when a block is given" do 187 | it "should yield sslv2, sslv3, tlsv1, tlsv1_1 and tlsv1_2" do 188 | expect { |b| 189 | subject.each_protocol(&b) 190 | }.to yield_successive_args( 191 | subject.sslv2, 192 | subject.sslv3, 193 | subject.tlsv1, 194 | subject.tlsv1_1, 195 | subject.tlsv1_2 196 | ) 197 | end 198 | end 199 | 200 | context "when no block is given" do 201 | it "should return an Enumerator" do 202 | expect(subject.each_protocol).to be_kind_of(Enumerator) 203 | end 204 | end 205 | end 206 | 207 | describe "#protocols" do 208 | it "should return sslv2, sslv3, tlsv1, tlsv1_1 and tlsv1_2" do 209 | expect(subject.protocols).to be == [ 210 | subject.sslv2, 211 | subject.sslv3, 212 | subject.tlsv1, 213 | subject.tlsv1_1, 214 | subject.tlsv1_2 215 | ] 216 | end 217 | end 218 | end 219 | -------------------------------------------------------------------------------- /spec/xml_examples.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'sslyze/xml' 3 | 4 | shared_examples_for "XML specs" do 5 | let(:path) { File.expand_path('spec/sslyze.xml') } 6 | let(:xml) { Nokogiri::XML(open(path)) } 7 | 8 | let(:xpath) { '/' } 9 | let(:node) { xml.at_xpath(xpath) } 10 | 11 | subject { described_class.new(node) } 12 | end 13 | -------------------------------------------------------------------------------- /spec/xml_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'xml_examples' 3 | require 'sslyze/xml' 4 | 5 | describe SSLyze::XML do 6 | include_examples "XML specs" 7 | 8 | describe ".parse" do 9 | it "should parse the contents of the file" do 10 | end 11 | end 12 | 13 | describe ".open" do 14 | it "should open the XML file" do 15 | end 16 | end 17 | 18 | describe "#version" do 19 | it "must parse the SSLyzeVersion attribute" do 20 | expect(subject.version).to match(/^1\.\d+\.\d+$/) 21 | end 22 | end 23 | 24 | describe "#total_scan_time" do 25 | it "must parse the totalScanTime attribute" do 26 | expect(subject.total_scan_time).to be_kind_of(Float) 27 | expect(subject.total_scan_time).to be > 0.0 28 | end 29 | end 30 | 31 | describe "#invalid_targets" do 32 | subject { super().invalid_targets } 33 | 34 | it "should return an Array of Strings" do 35 | expect(subject).to be_an(Array).and(all(be_a(described_class::InvalidTarget))) 36 | expect(subject.size).to be == 2 37 | end 38 | end 39 | 40 | describe "#each_invalid_target" do 41 | it "should iterate over each invalid target element under results" do 42 | expect { |b| 43 | subject.each_invalid_target(&b) 44 | }.to yield_successive_args( 45 | described_class::InvalidTarget, 46 | described_class::InvalidTarget 47 | ) 48 | end 49 | end 50 | 51 | describe "#targets" do 52 | it "should return an Array of Targets" do 53 | expect(subject.targets).to be_an(Array).and(all(be_a(described_class::Target))) 54 | end 55 | end 56 | 57 | describe "#target" do 58 | it "should return the first target" do 59 | expect(subject.target.host).to be == subject.targets.first.host 60 | end 61 | end 62 | end 63 | --------------------------------------------------------------------------------