Skip to content

Instantly share code, notes, and snippets.

@docwhat
Last active November 17, 2015 01:54
Show Gist options
  • Save docwhat/24f0add92c2f43d8ec9e to your computer and use it in GitHub Desktop.
Save docwhat/24f0add92c2f43d8ec9e to your computer and use it in GitHub Desktop.
Creates an SSL_CERT_FILE on OSX (when using Homebrew) that won't break JRuby. Make sure you run this with normal ruby, not JRuby! See https://github.com/jruby/jruby-openssl/issues/56
#!/usr/bin/env ruby
# General idea stolen with no regret from Homebrew's OpenSSL formula.
require 'optparse'
require 'forwardable'
require 'shellwords'
require 'openssl'
require 'digest/md5'
require 'digest/sha1'
KEYCHAINS = %w(
/Library/Keychains/System.keychain
/System/Library/Keychains/SystemRootCertificates.keychain
)
def unindent(str)
str.gsub(/^#{str[/\A\s*/]}/, '')
end
# My CLI application
class App
# Simple wrapper for displaying certs
class CertWrapper
extend Forwardable
# Work-around for https://github.com/ruby/openssl/issues/26
ASN1_STRFLGS_ESC_MSB = 4
def initialize(cert)
@cert = cert
end
def_delegator :@cert, :not_before
def_delegator :@cert, :not_after
def_delegator :@cert, :to_der
def_delegator :@cert, :to_pem
def extended_key_usage?
@cert.extensions.map(&:oid).count { |x| x == 'extendedKeyUsage' } > 1
end
def md5_fingerprint
Digest::MD5.hexdigest(to_der).upcase
end
def sha1_fingerprint
Digest::SHA1.hexdigest(to_der).upcase
end
def subject
# Work-around for https://github.com/ruby/openssl/issues/26
@cert
.subject
.to_s(OpenSSL::X509::Name::RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
.force_encoding(Encoding::UTF_8)
end
def issuer
# Work-around for https://github.com/ruby/openssl/issues/26
@cert
.issuer
.to_s(OpenSSL::X509::Name::RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
.force_encoding(Encoding::UTF_8)
end
def self_signed?
@cert.issuer == @cert.subject
end
def to_a # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
[].tap do |a|
a << "Subject: #{subject}"
a << "Issuer: #{issuer}" unless self_signed?
a << 'Validity'
a << " Not Before: #{not_before}"
a << " Not After: #{not_after}"
a << "MD5 Fingerprint: #{md5_fingerprint}"
a << "SHA1 Fingerprint: #{sha1_fingerprint}"
a << ''
a << to_pem
end
end
def to_s
to_a.join "\n"
end
end
attr_accessor :outfile
def initialize(args)
@args = Array(args)
@outfile = nil
end
def optparser # rubocop:disable Mecrics/MethodLength
@optparser ||= OptionParser.new do |opts|
opts.banner = "Usage: #{opts.program_name} [OPTIONS]"
opts.define_head unindent(<<-HEAD)
Scans the Apple Keychain for CA Certificates and generates a certs.pem
file. Suitable for using with the SSL_CERT_FILE environment variable.
HEAD
opts.separator ''
opts.on(
'-o',
'--output FILE',
String,
'A file to output the generated cert.pem to.'
) { |f| self.outfile = f }
opts.on(
'-O',
'--output-to-env',
String,
'Overwrite the ${SSL_CERT_FILE} with the generated cert.pem.'
) do |_f|
unless ENV.key? 'SSL_CERT_FILE'
STDERR.puts "${SSL_CERT_FILE} isn't set!"
exit 10
end
self.outfile = ENV['SSL_CERT_FILE']
end
end
end
def keychain_certs
filenames = KEYCHAINS.map { |f| Shellwords.escape f }
`security find-certificate -a -p #{filenames.join ' '}`
.scan(/-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/m)
.map { |pem| OpenSSL::X509::Certificate.new pem }
end
# We filter out:
# * Not yet valid certificates
# * Expired certificates
# * Certificates with multiple extendedKeyUsage extensions break Java/JRuby.
# See https://github.com/jruby/jruby-openssl/issues/56
def desired_certs
keychain_certs
.map { |c| CertWrapper.new c }
.reject { |cert| cert.not_before > Time.now }
.reject { |cert| cert.not_after < Time.now }
.reject(&:extended_key_usage?)
end
def run
optparser.parse!
cert_pem = desired_certs.map(&:to_s).join("\n\n")
if outfile
File.open(outfile, 'w') { |f| f.puts cert_pem }
else
puts cert_pem
end
end
end
App.new(ARGV).run if $PROGRAM_NAME == __FILE__
# EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment