diff --git a/Gemfile b/Gemfile index 122c549..f7be354 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,8 @@ source "https://rubygems.org" gemspec +gem 'ffi', '~> 1.9', :platforms => [:mswin, :mingw] + group :test do gem 'rake' end diff --git a/lib/restclient.rb b/lib/restclient.rb index 939cf39..0f544b6 100644 --- a/lib/restclient.rb +++ b/lib/restclient.rb @@ -18,6 +18,7 @@ require File.dirname(__FILE__) + '/restclient/raw_response' require File.dirname(__FILE__) + '/restclient/resource' require File.dirname(__FILE__) + '/restclient/payload' require File.dirname(__FILE__) + '/restclient/net_http_ext' +require File.dirname(__FILE__) + '/restclient/windows' # This module's static methods are the entry point for using the REST client. # diff --git a/lib/restclient/windows.rb b/lib/restclient/windows.rb new file mode 100644 index 0000000..499bdd3 --- /dev/null +++ b/lib/restclient/windows.rb @@ -0,0 +1,13 @@ +module RestClient + module Windows + def self.windows? + # Ruby only sets File::ALT_SEPARATOR on Windows, and the Ruby standard + # library uses that to test what platform it's on. + !!File::ALT_SEPARATOR + end + end +end + +if RestClient::Windows.windows? + require_relative './windows/root_certs' +end diff --git a/lib/restclient/windows/root_certs.rb b/lib/restclient/windows/root_certs.rb new file mode 100644 index 0000000..2d9ae64 --- /dev/null +++ b/lib/restclient/windows/root_certs.rb @@ -0,0 +1,105 @@ +require 'openssl' +require 'ffi' + +# Adapted from Puppet, Copyright (c) Puppet Labs Inc, +# licensed under the Apache License, Version 2.0. +# +# https://github.com/puppetlabs/puppet/blob/bbe30e0a/lib/puppet/util/windows/root_certs.rb + +# Represents a collection of trusted root certificates. +# +# @api public +class RestClient::Windows::RootCerts + include Enumerable + extend FFI::Library + + typedef :ulong, :dword + typedef :uintptr_t, :handle + + def initialize(roots) + @roots = roots + end + + # Enumerates each root certificate. + # @yieldparam cert [OpenSSL::X509::Certificate] each root certificate + # @api public + def each + @roots.each {|cert| yield cert} + end + + # Returns a new instance. + # @return [Puppet::Util::Windows::RootCerts] object constructed from current root certificates + def self.instance + new(self.load_certs) + end + + # Returns an array of root certificates. + # + # @return [Array<[OpenSSL::X509::Certificate]>] an array of root certificates + # @api private + def self.load_certs + certs = [] + + # This is based on a patch submitted to openssl: + # http://www.mail-archive.com/openssl-dev@openssl.org/msg26958.html + ptr = FFI::Pointer::NULL + store = CertOpenSystemStoreA(nil, "ROOT") + begin + while (ptr = CertEnumCertificatesInStore(store, ptr)) and not ptr.null? + context = CERT_CONTEXT.new(ptr) + cert_buf = context[:pbCertEncoded].read_bytes(context[:cbCertEncoded]) + begin + certs << OpenSSL::X509::Certificate.new(cert_buf) + rescue => detail + Puppet.warning("Failed to import root certificate: #{detail.inspect}") + end + end + ensure + CertCloseStore(store, 0) + end + + certs + end + + private + + # typedef ULONG_PTR HCRYPTPROV_LEGACY; + # typedef void *HCERTSTORE; + + class CERT_CONTEXT < FFI::Struct + layout( + :dwCertEncodingType, :dword, + :pbCertEncoded, :pointer, + :cbCertEncoded, :dword, + :pCertInfo, :pointer, + :hCertStore, :handle + ) + end + + # HCERTSTORE + # WINAPI + # CertOpenSystemStoreA( + # __in_opt HCRYPTPROV_LEGACY hProv, + # __in LPCSTR szSubsystemProtocol + # ); + ffi_lib :crypt32 + attach_function :CertOpenSystemStoreA, [:pointer, :string], :handle + + # PCCERT_CONTEXT + # WINAPI + # CertEnumCertificatesInStore( + # __in HCERTSTORE hCertStore, + # __in_opt PCCERT_CONTEXT pPrevCertContext + # ); + ffi_lib :crypt32 + attach_function :CertEnumCertificatesInStore, [:handle, :pointer], :pointer + + # BOOL + # WINAPI + # CertCloseStore( + # __in_opt HCERTSTORE hCertStore, + # __in DWORD dwFlags + # ); + ffi_lib :crypt32 + attach_function :CertCloseStore, [:handle, :dword], :bool +end