win32-certstore 0.2.4 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b07544eeb10d6d5d218064ac922283b7654a4e6a683999f645792ff27fa45d5
4
- data.tar.gz: c7be876bba7975484a85507e241d9d8f6c590194ea32290edc5189540e647f69
3
+ metadata.gz: 63c8c4f2aaa89a78a8123d98079710df407a3930b0f4b5a6ef3ff2232c11ba05
4
+ data.tar.gz: 8641dfff337fe7b702783becfcfbfbba6b2a66af189eeb511897c2f0cb5e7d6c
5
5
  SHA512:
6
- metadata.gz: 772ca2195c9e4aa5b522d2e1196730cfc3cb777a05779c99d17a770eae954280e5a555986ee8718fbf785a94d9490b55b419515548c0740ed7f08f20839cf43b
7
- data.tar.gz: 167436e45e6ae7b64cd4de5ce95eced68d7b16ed759585b3bd5a682a6f63b6c8b26c31c13c59e448006a6bb298ce89986bb33d987efcbbaee3959f6542ff733f
6
+ metadata.gz: 4f255e439feee57642565bd9fca87f19140b5f95c38eed0a1e8621326749dd5274c0b72c8211bddfbec265820bff4b8d2ad9460ebbf5c30fa1d6607a7e81f204
7
+ data.tar.gz: 4efd363fb264fc8501f0f9e105e79f7537319838aafc0cb751387a73360386186d326dedd51522907aa131fe74c1b3ba07bd7b3f6f16a1283f5f59203d96a6a7
@@ -31,18 +31,21 @@ module Win32
31
31
 
32
32
  attr_accessor :store_name
33
33
 
34
- def initialize(store_name)
34
+ # Initializes a new instance of a certificate store.
35
+ # takes 2 parameters - the store name (My, Root, etc) and the location (CurrentUser or LocalMachine), it defaults to LocalMachine for backwards compatibility
36
+ def initialize(store_name, store_location: CERT_SYSTEM_STORE_LOCAL_MACHINE)
35
37
  @store_name = store_name
36
- @certstore_handler = open(store_name)
38
+ @store_location = store_location
39
+ @certstore_handler = open(store_name, store_location: store_location)
37
40
  end
38
41
 
39
42
  # To open given certificate store
40
- def self.open(store_name)
43
+ def self.open(store_name, store_location: CERT_SYSTEM_STORE_LOCAL_MACHINE)
41
44
  validate_store(store_name)
42
45
  if block_given?
43
- yield new(store_name)
46
+ yield new(store_name, store_location: store_location)
44
47
  else
45
- new(store_name)
48
+ new(store_name, store_location: store_location)
46
49
  end
47
50
  end
48
51
 
@@ -60,11 +63,12 @@ module Win32
60
63
  #
61
64
  # @param path [String] Path of the certificate that should be imported
62
65
  # @param password [String] Password of the certificate if it is protected
66
+ # @param key_properties [Integer] dwFlags used to specify properties of the pfx key, see certstore/store_base.rb cert_add_pfx function
63
67
  #
64
68
  # @return [Boolean]
65
69
  #
66
- def add_pfx(path, password)
67
- cert_add_pfx(certstore_handler, path, password)
70
+ def add_pfx(path, password, key_properties = 0)
71
+ cert_add_pfx(certstore_handler, path, password, key_properties)
68
72
  end
69
73
 
70
74
  # Return `OpenSSL::X509` certificate object
@@ -74,6 +78,18 @@ module Win32
74
78
  cert_get(certificate_thumbprint)
75
79
  end
76
80
 
81
+ # Returns a filepath to a PKCS12 container. The filepath is in a temporary folder so normal housekeeping by the OS should clear it.
82
+ # However, you should delete it yourself anyway.
83
+ # @param certificate_thumbprint [String] Is the thumbprint of the pfx blob you want to capture
84
+ # @param store_location: [String] A location in the Cert store where the pfx is located, typically 'LocalMachine'
85
+ # @param export_password: [String] The password to export with. P12 objects are an encrypted container that have a private key in \
86
+ # them and a password is required to export them.
87
+ # @param output_path: [String] The path where the you want P12 exported to.
88
+ # @return [Object] of certificate set in PKSC12 format at the path specified above
89
+ def get_pfx(certificate_thumbprint, store_location: @store_location, export_password:, output_path: "")
90
+ get_cert_pfx(certificate_thumbprint, store_location: store_location, export_password: export_password, output_path: output_path)
91
+ end
92
+
77
93
  # Returns all the certificates in a store
78
94
  # @param [nil]
79
95
  # @return [Array] array of certificates list
@@ -117,8 +133,9 @@ module Win32
117
133
  attr_reader :certstore_handler
118
134
 
119
135
  # To open certstore and return open certificate store pointer
120
- def open(store_name)
121
- certstore_handler = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, nil, CERT_SYSTEM_STORE_LOCAL_MACHINE, wstring(store_name))
136
+
137
+ def open(store_name, store_location: CERT_SYSTEM_STORE_LOCAL_MACHINE)
138
+ certstore_handler = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, nil, store_location, wstring(store_name))
122
139
  unless certstore_handler
123
140
  last_error = FFI::LastError.error
124
141
  raise SystemCallError.new("Unable to open the Certificate Store `#{store_name}`.", last_error)
@@ -14,7 +14,7 @@
14
14
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
- require "openssl"
17
+ require "openssl" unless defined?(OpenSSL)
18
18
 
19
19
  module Win32
20
20
  class Certstore
@@ -16,7 +16,7 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
- require "ffi"
19
+ require "ffi" unless defined?(FFI)
20
20
 
21
21
  module Win32
22
22
  class Certstore
@@ -88,6 +88,9 @@ module Win32
88
88
 
89
89
  CERT_STORE_PROV_SYSTEM = 10
90
90
  CERT_SYSTEM_STORE_LOCAL_MACHINE = 0x00020000
91
+ CERT_SYSTEM_STORE_CURRENT_USER = 0x00010000
92
+ CERT_SYSTEM_STORE_SERVICES = 0x00050000
93
+ CERT_SYSTEM_STORE_USERS = 0x00060000
91
94
 
92
95
  # Define ffi pointer
93
96
  HCERTSTORE = FFI::TypeDefs[:pointer]
@@ -113,17 +116,17 @@ module Win32
113
116
 
114
117
  class FILETIME < FFI::Struct
115
118
  layout :dwLowDateTime, DWORD,
116
- :dwHighDateTime, DWORD
119
+ :dwHighDateTime, DWORD
117
120
  end
118
121
 
119
122
  class CRYPT_INTEGER_BLOB < FFI::Struct
120
123
  layout :cbData, DWORD, # Count, in bytes, of data
121
- :pbData, :pointer # Pointer to data buffer
124
+ :pbData, :pointer # Pointer to data buffer
122
125
  end
123
126
 
124
127
  class CRYPT_NAME_BLOB < FFI::Struct
125
128
  layout :cbData, DWORD, # Count, in bytes, of data
126
- :pbData, :pointer # Pointer to data buffer
129
+ :pbData, :pointer # Pointer to data buffer
127
130
  def initialize(str = nil)
128
131
  super(nil)
129
132
  if str
@@ -134,7 +137,7 @@ module Win32
134
137
 
135
138
  class CRYPT_HASH_BLOB < FFI::Struct
136
139
  layout :cbData, DWORD, # Count, in bytes, of data
137
- :pbData, :pointer # Pointer to data buffer
140
+ :pbData, :pointer # Pointer to data buffer
138
141
 
139
142
  def initialize(str = nil)
140
143
  super(nil)
@@ -151,7 +154,7 @@ module Win32
151
154
 
152
155
  class CRYPT_DATA_BLOB < FFI::Struct
153
156
  layout :cbData, DWORD, # Count, in bytes, of data
154
- :pbData, :pointer # Pointer to data buffer
157
+ :pbData, :pointer # Pointer to data buffer
155
158
 
156
159
  def initialize(str = nil)
157
160
  super(nil)
@@ -164,47 +167,47 @@ module Win32
164
167
 
165
168
  class CERT_EXTENSION < FFI::Struct
166
169
  layout :pszObjId, LPTSTR,
167
- :fCritical, BOOL,
168
- :Value, CRYPT_INTEGER_BLOB
170
+ :fCritical, BOOL,
171
+ :Value, CRYPT_INTEGER_BLOB
169
172
  end
170
173
 
171
174
  class CRYPT_BIT_BLOB < FFI::Struct
172
175
  layout :cbData, DWORD,
173
- :pbData, BYTE,
174
- :cUnusedBits, DWORD
176
+ :pbData, BYTE,
177
+ :cUnusedBits, DWORD
175
178
  end
176
179
 
177
180
  class CRYPT_ALGORITHM_IDENTIFIER < FFI::Struct
178
181
  layout :pszObjId, LPSTR,
179
- :Parameters, CRYPT_INTEGER_BLOB
182
+ :Parameters, CRYPT_INTEGER_BLOB
180
183
  end
181
184
 
182
185
  class CERT_PUBLIC_KEY_INFO < FFI::Struct
183
186
  layout :Algorithm, CRYPT_ALGORITHM_IDENTIFIER,
184
- :PublicKey, CRYPT_BIT_BLOB
187
+ :PublicKey, CRYPT_BIT_BLOB
185
188
  end
186
189
 
187
190
  class CERT_INFO < FFI::Struct
188
191
  layout :dwVersion, DWORD,
189
- :SerialNumber, CRYPT_INTEGER_BLOB,
190
- :SignatureAlgorithm, CRYPT_ALGORITHM_IDENTIFIER,
191
- :Issuer, CRYPT_NAME_BLOB,
192
- :NotBefore, FILETIME,
193
- :NotAfter, FILETIME,
194
- :Subject, CRYPT_NAME_BLOB,
195
- :SubjectPublicKeyInfo, CERT_PUBLIC_KEY_INFO,
196
- :IssuerUniqueId, CRYPT_BIT_BLOB,
197
- :SubjectUniqueId, CRYPT_BIT_BLOB,
198
- :cExtension, DWORD,
199
- :rgExtension, CERT_EXTENSION
192
+ :SerialNumber, CRYPT_INTEGER_BLOB,
193
+ :SignatureAlgorithm, CRYPT_ALGORITHM_IDENTIFIER,
194
+ :Issuer, CRYPT_NAME_BLOB,
195
+ :NotBefore, FILETIME,
196
+ :NotAfter, FILETIME,
197
+ :Subject, CRYPT_NAME_BLOB,
198
+ :SubjectPublicKeyInfo, CERT_PUBLIC_KEY_INFO,
199
+ :IssuerUniqueId, CRYPT_BIT_BLOB,
200
+ :SubjectUniqueId, CRYPT_BIT_BLOB,
201
+ :cExtension, DWORD,
202
+ :rgExtension, CERT_EXTENSION
200
203
  end
201
204
 
202
205
  class CERT_CONTEXT < FFI::Struct
203
206
  layout :dwCertEncodingType, DWORD,
204
- :pbCertEncoded, BYTE,
205
- :cbCertEncoded, DWORD,
206
- :pCertInfo, CERT_INFO,
207
- :hCertStore, HCERTSTORE
207
+ :pbCertEncoded, BYTE,
208
+ :cbCertEncoded, DWORD,
209
+ :pCertInfo, CERT_INFO,
210
+ :hCertStore, HCERTSTORE
208
211
  end
209
212
 
210
213
  ###############################################################################
@@ -21,21 +21,52 @@ module Win32
21
21
  class Certstore
22
22
  module Mixin
23
23
  module Helper
24
-
25
- # PSCommand to search certificate from thumbprint and convert in pem
26
- def cert_ps_cmd(thumbprint, store_name)
24
+ # PSCommand to search certificate from thumbprint and either turn it into a pem or return a path to a pfx object
25
+ def cert_ps_cmd(thumbprint, store_location: "LocalMachine", export_password: "1234", output_path: "")
27
26
  <<-EOH
28
- $content = $null
29
- $cert = Get-ChildItem Cert:\\LocalMachine\\'#{store_name}' -Recurse | Where { $_.Thumbprint -eq '#{thumbprint}' }
30
- if($cert -ne $null)
31
- {
32
- $content = @(
33
- '-----BEGIN CERTIFICATE-----'
34
- [System.Convert]::ToBase64String($cert.RawData, 'InsertLineBreaks')
35
- '-----END CERTIFICATE-----'
36
- )
27
+ $cert = Get-ChildItem Cert:\'#{store_location}' -Recurse | Where { $_.Thumbprint -eq '#{thumbprint}' }
28
+
29
+ # The function and the code below test to see if a) the cert has a private key and b) it has a
30
+ # Enhanced Usage of Client Auth. Those 2 attributes would mean this is a pfx-able object
31
+ function test_cert_values{
32
+ $usagelist = ($cert).EnhancedKeyUsageList
33
+ foreach($use in $usagelist){
34
+ if($use.FriendlyName -like "Client Authentication" ){
35
+ return $true
36
+ }
37
+ }
38
+ return $false
39
+ }
40
+
41
+ $result = test_cert_values
42
+
43
+ $output_path = "#{output_path}"
44
+ if([string]::IsNullOrEmpty($output_path)){
45
+ $temproot = [System.IO.Path]::GetTempPath()
46
+ }
47
+ else{
48
+ $temproot = $output_path
49
+ }
50
+
51
+ if((($cert).HasPrivateKey) -and ($result -eq $true)){
52
+ $file_name = '#{thumbprint}'
53
+ $file_path = $(Join-Path -Path $temproot -ChildPath "$file_name.pfx")
54
+ $mypwd = ConvertTo-SecureString -String '#{export_password}' -Force -AsPlainText
55
+ $cert | Export-PfxCertificate -FilePath $file_path -Password $mypwd | Out-Null
56
+ $file_path
57
+ }
58
+ else {
59
+ $content = $null
60
+ if($cert -ne $null)
61
+ {
62
+ $content = @(
63
+ '-----BEGIN CERTIFICATE-----'
64
+ [System.Convert]::ToBase64String($cert.RawData, 'InsertLineBreaks')
65
+ '-----END CERTIFICATE-----'
66
+ )
67
+ }
68
+ $content
37
69
  }
38
- $content
39
70
  EOH
40
71
  end
41
72
 
@@ -43,7 +74,6 @@ module Win32
43
74
  def valid_duration?(cert_obj)
44
75
  cert_obj.not_before < Time.now.utc && cert_obj.not_after > Time.now.utc
45
76
  end
46
-
47
77
  end
48
78
  end
49
79
  end
@@ -15,12 +15,12 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
- require "mixlib/shellout"
18
+ require "mixlib/shellout" unless defined?(Mixlib::ShellOut)
19
19
 
20
20
  module Win32
21
21
  class Certstore
22
22
  module Mixin
23
- module ShellOut
23
+ module ShellExec
24
24
  def shell_out_command(*command_args)
25
25
  cmd = Mixlib::ShellOut.new(*command_args)
26
26
  cmd.live_stream
@@ -28,6 +28,7 @@ module Win32
28
28
  if cmd.error!
29
29
  raise Mixlib::ShellOut::ShellCommandFailed, cmd.error!
30
30
  end
31
+
31
32
  cmd
32
33
  end
33
34
 
@@ -38,7 +39,7 @@ module Win32
38
39
  # @param script [String] script to run
39
40
  # @param options [Hash] options hash
40
41
  # @return [Mixlib::Shellout] mixlib-shellout object
41
- def powershell_out(*command_args)
42
+ def powershell_exec(*command_args)
42
43
  script = command_args.first
43
44
  options = command_args.last.is_a?(Hash) ? command_args.last : nil
44
45
 
@@ -51,8 +52,8 @@ module Win32
51
52
  # @param script [String] script to run
52
53
  # @param options [Hash] options hash
53
54
  # @return [Mixlib::Shellout] mixlib-shellout object
54
- def powershell_out!(*command_args)
55
- cmd = powershell_out(*command_args)
55
+ def powershell_exec!(*command_args)
56
+ cmd = powershell_exec(*command_args)
56
57
  cmd.error!
57
58
  cmd
58
59
  end
@@ -96,7 +97,7 @@ module Win32
96
97
  "-InputFormat None",
97
98
  ]
98
99
 
99
- "powershell.exe #{flags.join(' ')} -Command \"#{script.gsub('"', '\"')}\""
100
+ "powershell.exe #{flags.join(" ")} -Command \"#{script.gsub('"', '\"')}\""
100
101
  end
101
102
  end
102
103
  end
@@ -17,10 +17,10 @@
17
17
 
18
18
  require_relative "mixin/crypto"
19
19
  require_relative "mixin/string"
20
- require_relative "mixin/shell_out"
20
+ require_relative "mixin/shell_exec"
21
21
  require_relative "mixin/unicode"
22
- require "openssl"
23
- require "json"
22
+ require "openssl" unless defined?(OpenSSL)
23
+ require "json" unless defined?(JSON)
24
24
 
25
25
  module Win32
26
26
  class Certstore
@@ -28,7 +28,7 @@ module Win32
28
28
  include Win32::Certstore::Mixin::Crypto
29
29
  include Win32::Certstore::Mixin::Assertions
30
30
  include Win32::Certstore::Mixin::String
31
- include Win32::Certstore::Mixin::ShellOut
31
+ include Win32::Certstore::Mixin::ShellExec
32
32
  include Win32::Certstore::Mixin::Unicode
33
33
  include Win32::Certstore::Mixin::Helper
34
34
 
@@ -57,22 +57,25 @@ module Win32
57
57
  # @param certstore_handler [FFI::Pointer] Handle of the store where certificate should be imported
58
58
  # @param path [String] Path of the certificate that should be imported
59
59
  # @param password [String] Password of the certificate
60
+ # @param key_properties [Integer] dwFlags used to specify properties of the pfx key, see link above
60
61
  #
61
62
  # @return [Boolean]
62
63
  #
63
64
  # @raise [SystemCallError] when Crypt API would not be able to perform some action
64
65
  #
65
- def cert_add_pfx(certstore_handler, path, password = "")
66
+ def cert_add_pfx(certstore_handler, path, password = "", key_properties = 0)
67
+ cert_added = false
66
68
  # Imports a PFX BLOB and returns the handle of a store
67
- pfx_cert_store = PFXImportCertStore(CRYPT_DATA_BLOB.new(File.binread(path)), wstring(password), 0)
69
+ pfx_cert_store = PFXImportCertStore(CRYPT_DATA_BLOB.new(File.binread(path)), wstring(password), key_properties)
68
70
  raise if pfx_cert_store.null?
69
- # Find the certificate context in certificate store
70
- cert_context = CertFindCertificateInStore(pfx_cert_store, ENCODING_TYPE, 0, CERT_FIND_ANY, nil, nil)
71
- raise if cert_context.null?
72
- # Add certificate context to the certificate store
73
- args = add_certcontxt_args(certstore_handler, cert_context)
74
- cert_added = CertAddCertificateContextToStore(*args)
75
- raise unless cert_added
71
+
72
+ # Find all the certificate contexts in certificate store and add them ino the store
73
+ while (cert_context = CertEnumCertificatesInStore(pfx_cert_store, cert_context)) && (not cert_context.null?)
74
+ # Add certificate context to the certificate store
75
+ args = add_certcontxt_args(certstore_handler, cert_context)
76
+ cert_added = CertAddCertificateContextToStore(*args)
77
+ raise unless cert_added
78
+ end
76
79
  cert_added
77
80
  rescue
78
81
  lookup_error("Add a PFX")
@@ -122,7 +125,7 @@ module Win32
122
125
  begin
123
126
  cert_args = cert_find_args(store_handler, thumbprint)
124
127
  pcert_context = CertFindCertificateInStore(*cert_args)
125
- if !pcert_context.null?
128
+ unless pcert_context.null?
126
129
  cert_delete_flag = CertDeleteCertificateFromStore(CertDuplicateCertificateContext(pcert_context)) || lookup_error
127
130
  end
128
131
  CertFreeCertificateContext(pcert_context)
@@ -148,6 +151,7 @@ module Win32
148
151
  # search_token => CN, RDN or any certificate attribute
149
152
  def cert_search(store_handler, search_token)
150
153
  raise ArgumentError, "Invalid search token" if !search_token || search_token.strip.empty?
154
+
151
155
  certificate_list = []
152
156
  begin
153
157
  while (pcert_context = CertEnumCertificatesInStore(store_handler, pcert_context)) && !pcert_context.null?
@@ -216,6 +220,7 @@ module Win32
216
220
  # Verify OpenSSL::X509::Certificate object
217
221
  def verify_certificate(cert_pem)
218
222
  return "Certificate not found" if cert_pem.empty?
223
+
219
224
  valid_duration?(build_openssl_obj(cert_pem))
220
225
  end
221
226
 
@@ -226,7 +231,23 @@ module Win32
226
231
 
227
232
  # Get certificate pem
228
233
  def get_cert_pem(thumbprint)
229
- get_data = powershell_out!(cert_ps_cmd(thumbprint, store_name))
234
+ converted_store = if @store_location == CERT_SYSTEM_STORE_LOCAL_MACHINE
235
+ "LocalMachine"
236
+ else
237
+ "CurrentUser"
238
+ end
239
+ get_data = powershell_exec!(cert_ps_cmd(thumbprint, store_location: converted_store))
240
+ get_data.stdout
241
+ end
242
+
243
+ # Get PFX object
244
+ def get_cert_pfx(thumbprint, store_location: CERT_SYSTEM_STORE_LOCAL_MACHINE, export_password:, output_path: )
245
+ converted_store = if store_location == CERT_SYSTEM_STORE_LOCAL_MACHINE
246
+ "LocalMachine"
247
+ else
248
+ "CurrentUser"
249
+ end
250
+ get_data = powershell_exec!(cert_ps_cmd(thumbprint, export_password: export_password, store_location: converted_store, output_path: output_path))
230
251
  get_data.stdout
231
252
  end
232
253
 
@@ -1,6 +1,6 @@
1
1
  module Win32
2
2
  class Certstore
3
- VERSION = "0.2.4".freeze
3
+ VERSION = "0.6.1".freeze
4
4
  MAJOR, MINOR, TINY = VERSION.split(".")
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: win32-certstore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chef Software
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-04 00:00:00.000000000 Z
11
+ date: 2021-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -79,7 +79,7 @@ files:
79
79
  - lib/win32/certstore/mixin/assertions.rb
80
80
  - lib/win32/certstore/mixin/crypto.rb
81
81
  - lib/win32/certstore/mixin/helper.rb
82
- - lib/win32/certstore/mixin/shell_out.rb
82
+ - lib/win32/certstore/mixin/shell_exec.rb
83
83
  - lib/win32/certstore/mixin/string.rb
84
84
  - lib/win32/certstore/mixin/unicode.rb
85
85
  - lib/win32/certstore/store_base.rb
@@ -97,16 +97,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
97
97
  requirements:
98
98
  - - ">="
99
99
  - !ruby/object:Gem::Version
100
- version: '2.3'
100
+ version: '2.5'
101
101
  required_rubygems_version: !ruby/object:Gem::Requirement
102
102
  requirements:
103
103
  - - ">="
104
104
  - !ruby/object:Gem::Version
105
105
  version: '0'
106
106
  requirements: []
107
- rubyforge_project:
108
- rubygems_version: 2.7.6
107
+ rubygems_version: 3.1.4
109
108
  signing_key:
110
109
  specification_version: 4
111
- summary: Ruby library for accessing the certificate store on Windows.
110
+ summary: Ruby library for accessing the certificate stores on Windows.
112
111
  test_files: []