Tebako

Using SSL in applications packaged with Aibika

Author’s picture Maxim Samsonov on 06 Sep 2023

Introduction

Aibika is a renewed tool for Windows that creates a self-extracting, self-running executable containing the Ruby interpreter, application source code, and any additional Ruby libraries or dynamic link libraries (DLLs) needed.

While Aibika can select and include files to be included in the package automatically or statically, some resources require additional processing.

SSL certificate verification

Let’s consider a simple example of downloading a file from the Internet through HTTPS.

download.rb:

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'open-uri'
require 'pathname'

base_url = 'https://github.com/oneclick/rubyinstaller2/releases/download/RubyInstaller-3.1.4-1/'
fn = 'rubyinstaller-3.1.4-1-x64.exe'
url = URI.join(base_url, fn)
filename = Pathname.new(Dir.pwd).join(fn)

puts "Downloading #{url} to #{filename.relative_path_from(Dir.pwd)} ..."

File.open(filename, "wb") do |file|
  URI.open(url, "rb") do |url_file|
    file.write(url_file.read)
  end
rescue OpenURI::HTTPError => e
  puts "OpenURI HTTP error: #{e.message}"
rescue OpenSSL::SSL::SSLError => e
  puts "OpenSSL SSL error: #{e.message}"
end

This script works if executed 'as is' outside Aikiba.

However, it ends with an error when it is run as packaged by Aibika:

$ ./download.rb
Downloading https://github.com/oneclick/rubyinstaller2/releases/download/
  RubyInstaller-3.1.4-1/rubyinstaller-3.1.4-1-x64.exe
  to rubyinstaller-3.1.4-1-x64.exe ...

OpenSSL SSL error: SSL_connect returned=1 errno=0 peeraddr=140.82.121.4:443
  state=error: certificate verify failed
  (unable to get local issuer certificate)

This is because the package created by Aibika runs in a virtual environment, isolated from the "normal" host environment.

The problem is root certificates are housed in the host environment, and the SSL processing routines in Ruby in the Aibika package (through the packaged openssl gem) does not know how to find these certificates on the host.

Supporting SSL verification

To solve the problem described above, two approaches are possible:

  • deploy Aibika package with root certificates included

  • let the packaged program look for local root certificates

Since the purpose of packaging is to create a program that has no external dependencies, typically, the first option is suitable.

To achieve the first option, we can bundle the cacert.pem file, which is the the Mozilla certificate package provided by the curl project, together with the desired Ruby script using Aikiba (download.rb is the script from the example above):

$ aibika download.rb cacert.pem

We need to take an additional step. We need to tell Aikiba’s packaged Ruby to point to the new bundled certificate file by updating the Net::HTTP method.

Fortunately, Ruby supports monkey-patching which allows us to do the following override by intercept the evaluation of the Net::HTTP class and change its behavior.

download.rb packaged in Aikiba:

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'net/https'
require 'open-uri'
require 'pathname'

pem_path = Pathname.new(__FILE__).expand_path.join("cacert.pem")
Net::HTTP.class_eval do
  alias _use_ssl= use_ssl=

  def use_ssl= boolean
    self.ca_file = pem_path.to_s
    self.verify_mode = OpenSSL::SSL::VERIFY_PEER
    self._use_ssl = boolean
  end
end

base_url = 'https://github.com/oneclick/rubyinstaller2/releases/download/RubyInstaller-3.1.4-1/'
fn = 'rubyinstaller-3.1.4-1-x64.exe'
url = URI.join(base_url, fn)
filename = Pathname.new(Dir.pwd).join(fn)

puts "Downloading #{url} to #{filename.relative_path_from(Dir.pwd)} ..."

File.open(filename, "wb") do |file|
  URI.open(url, "rb") do |url_file|
    file.write(url_file.read)
  end
rescue OpenURI::HTTPError => e
  puts "OpenURI HTTP error: #{e.message}"
rescue OpenSSL::SSL::SSLError => e
  puts "OpenSSL SSL error: #{e.message}"
end

Another, more "traditional", option would be to create an OpenSSL configuration (an instance of the OpenSSL::Config class) and find a way to pass it through OpenURI, URI, Net::HTTP down to the OpenSSL instance.

Conclusion

While Aibika does its best to ensure that each image contains all the files needed to run a bundled application, there are non-trivial circumstances where external artifacts require additional processing during operation. packaging and/or execution. The SSL verification process is one such artifact.