Last active
November 17, 2015 01:54
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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