Class: Aspera::WebServerSimple

Inherits:
WEBrick::HTTPServer
  • Object
show all
Defined in:
lib/aspera/web_server_simple.rb

Overview

Simple WEBrick server with HTTPS support

Direct Known Subclasses

WebAuth

Constant Summary collapse

PARAMS =
%i[cert key chain].freeze
DEFAULT_URL =
'http://localhost:8080'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(uri, cert: nil, key: nil, chain: nil) ⇒ WebServerSimple

Returns a new instance of WebServerSimple.

Parameters:

  • url (URI)

    Local address where server will listen (use scheme, host and port only)

  • cert (String) (defaults to: nil)

    Path to certificate file, either with extension .p12 or .pfx, else assumed PEM

  • key (String) (defaults to: nil)

    Path to key file (PEM) or passphrase (pkcs12)

  • chain (String) (defaults to: nil)

    Path to certificate chain file (PEM only)



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/aspera/web_server_simple.rb', line 55

def initialize(uri, cert: nil, key: nil, chain: nil)
  Aspera.assert_type(uri, URI)
  @uri = uri
  # see https://www.rubydoc.info/stdlib/webrick/WEBrick/Config
  webrick_options = {
    BindAddress: @uri.host,
    Port:        @uri.port,
    Logger:      Log.log,
    AccessLog:   [[self, WEBrick::AccessLog::COMMON_LOG_FORMAT]] # replace default access log to call local method "<<" below
  }
  case @uri.scheme
  when 'http'
    Log.log.debug('HTTP mode')
  when 'https'
    webrick_options[:SSLEnable] = true
    if cert.nil? && key.nil?
      webrick_options[:SSLCertName] = [['CN', WEBrick::Utils.getservername]]
    elsif cert && PKCS12_EXT.include?(File.extname(cert).downcase)
      # PKCS12
      Log.log.debug('Using PKCS12 certificate')
      raise 'PKCS12 requires a key (password)' if key.nil?
      pkcs12 = OpenSSL::PKCS12.new(File.read(cert), key)
      webrick_options[:SSLCertificate] = pkcs12.certificate
      webrick_options[:SSLPrivateKey] = pkcs12.key
      webrick_options[:SSLExtraChainCert] = pkcs12.ca_certs
    else
      Log.log.debug('Using PEM certificate')
      webrick_options[:SSLPrivateKey] = if key.nil?
        OpenSSL::PKey::RSA.new(4096)
      else
        OpenSSL::PKey::RSA.new(File.read(key))
      end
      webrick_options[:SSLCertificate] = if cert.nil?
        self.class.self_signed_cert(webrick_options[:SSLPrivateKey])
      else
        OpenSSL::X509::Certificate.new(File.read(cert))
      end
      webrick_options[:SSLExtraChainCert] = read_chain_file(chain) unless chain.nil?
      raise 'key and cert do not match' unless webrick_options[:SSLCertificate].public_key.to_der == webrick_options[:SSLPrivateKey].public_key.to_der
    end
  end
  # call constructor of parent class, but capture STDERR
  # self signed certificate generates characters on STDERR
  # see create_self_signed_cert in webrick/ssl.rb
  Log.capture_stderr{super(webrick_options)}
end

Class Method Details

.read_chain_file(chain) ⇒ Object

Returns a list of Certificates from chain file.

Returns:

  • a list of Certificates from chain file



46
47
48
# File 'lib/aspera/web_server_simple.rb', line 46

def read_chain_file(chain)
  File.read(chain).scan(/-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/m).map{ |i| OpenSSL::X509::Certificate.new(i)}
end

.self_signed_cert(private_key, digest: 'SHA256') ⇒ Object

Generate or fill and self sign certificate



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/aspera/web_server_simple.rb', line 24

def self_signed_cert(private_key, digest: 'SHA256')
  cert = OpenSSL::X509::Certificate.new
  cert.subject = cert.issuer = OpenSSL::X509::Name.parse(GENERIC_ISSUER)
  cert.not_before = Time.now - CLOCK_SKEW_OFFSET_SEC
  cert.not_after  = cert.not_before + ONE_YEAR_SECONDS
  cert.public_key = private_key.public_key
  cert.serial = 0x0
  cert.version = 2
  ef = OpenSSL::X509::ExtensionFactory.new
  ef.issuer_certificate = cert
  ef.subject_certificate = cert
  cert.extensions = [
    ef.create_extension('basicConstraints', 'CA:TRUE', true),
    ef.create_extension('subjectKeyIdentifier', 'hash')
    # ef.create_extension('keyUsage', 'cRLSign,keyCertSign', true),
  ]
  cert.add_extension(ef.create_extension('authorityKeyIdentifier', 'keyid:always,issuer:always'))
  cert.sign(private_key, OpenSSL::Digest.new(digest))
  cert
end

Instance Method Details

#<<(access_log) ⇒ Object

log web server access ( option AccessLog )



112
113
114
# File 'lib/aspera/web_server_simple.rb', line 112

def <<(access_log)
  Log.log.debug{"webrick log #{access_log.chomp}"}
end

#startObject

blocking



103
104
105
106
107
108
109
# File 'lib/aspera/web_server_simple.rb', line 103

def start
  Log.log.info{"Listening on #{@uri}"}
  # kill (-TERM) for graceful shutdown
  handler = proc{shutdown}
  %i{INT TERM}.each{ |sig| trap(sig, &handler)}
  super
end