win32-certstore 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +225 -0
- data/lib/win32-certstore.rb +18 -0
- data/lib/win32/certstore.rb +129 -0
- data/lib/win32/certstore/mixin/assertions.rb +90 -0
- data/lib/win32/certstore/mixin/crypto.rb +203 -0
- data/lib/win32/certstore/mixin/helper.rb +50 -0
- data/lib/win32/certstore/mixin/shell_out.rb +104 -0
- data/lib/win32/certstore/mixin/string.rb +71 -0
- data/lib/win32/certstore/mixin/unicode.rb +50 -0
- data/lib/win32/certstore/store_base.rb +214 -0
- data/lib/win32/certstore/version.rb +6 -0
- metadata +125 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3f88b178dce120110256036f506c0643650a851f
|
4
|
+
data.tar.gz: 26e4d3283456a4e3f4297b5d076df2e1a2f677d5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2768217e40d97a78ca9c0f393ac1afe154e14f833a7f549fcb7b478ffab5666a2c051df349bbceb9ce32810863d1a366760f4b397097c2a5b9ee52fb1bf7a9f4
|
7
|
+
data.tar.gz: 853495d9d5334d56b268fa34d9b3b4c9e5afa3bf913f7b817b67aec6e5c40516ff17b4060ec63da94eec7f97ffc6dc253006a6596f86d57acb88342fa2d605f2
|
data/README.md
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
# win32-certstore
|
2
|
+
Ruby library for accessing the certificate store on Microsoft Windows:
|
3
|
+
|
4
|
+
## Subcommands
|
5
|
+
|
6
|
+
This library provides the following features.
|
7
|
+
|
8
|
+
### Open certificate store
|
9
|
+
|
10
|
+
Any valid certificate store can be opened in two ways:
|
11
|
+
|
12
|
+
```
|
13
|
+
Win32::Certstore.open("Root") do |store|
|
14
|
+
//your code should be here!
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
or
|
19
|
+
|
20
|
+
```
|
21
|
+
store = Win32::Certstore.open("Root")
|
22
|
+
```
|
23
|
+
|
24
|
+
### Add certificate
|
25
|
+
|
26
|
+
This method adds a new certificate to an open certificate store.
|
27
|
+
|
28
|
+
```
|
29
|
+
Input - Certificate Object (OpenSSL::X509)
|
30
|
+
Return - True/False
|
31
|
+
```
|
32
|
+
|
33
|
+
**Notes: The certificate must be passed as an `OpenSSL::X509` object.**
|
34
|
+
|
35
|
+
```
|
36
|
+
raw = File.read "C:\GlobalSignRootCA.pem"
|
37
|
+
certificate_object = OpenSSL::X509::Certificate.new raw
|
38
|
+
|
39
|
+
Win32::Certstore.open('Root') do |store|
|
40
|
+
store.add(certificate_object)
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
or
|
45
|
+
|
46
|
+
```
|
47
|
+
raw = File.read "C:\GlobalSignRootCA.pem"
|
48
|
+
certificate_object = OpenSSL::X509::Certificate.new raw
|
49
|
+
|
50
|
+
store = Win32::Certstore.open('Root')
|
51
|
+
store.add(certificate_object)
|
52
|
+
store.close
|
53
|
+
```
|
54
|
+
|
55
|
+
### Get certificate
|
56
|
+
|
57
|
+
Gets a certificate from an open certificate store and returns it as an `OpenSSL::X509` object.
|
58
|
+
|
59
|
+
```
|
60
|
+
Input - Certificate thumbprint
|
61
|
+
Return - Certificate Object (OpenSSL::X509)
|
62
|
+
```
|
63
|
+
|
64
|
+
```
|
65
|
+
Win32::Certstore.open("Root") do |store|
|
66
|
+
store.get(certificate_thumbprint)
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
or
|
71
|
+
|
72
|
+
```
|
73
|
+
store = Win32::Certstore.open("Root")
|
74
|
+
store.get(certificate_thumbprint)
|
75
|
+
store.close
|
76
|
+
```
|
77
|
+
|
78
|
+
### List certificates
|
79
|
+
|
80
|
+
Lists all certificates in a certificate store.
|
81
|
+
|
82
|
+
```
|
83
|
+
Input - NA
|
84
|
+
Return - Certificate List in JSON format.
|
85
|
+
```
|
86
|
+
|
87
|
+
```
|
88
|
+
Win32::Certstore.open("Root") do |store|
|
89
|
+
store.list
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
or
|
94
|
+
|
95
|
+
```
|
96
|
+
store = Win32::Certstore.open("Root")
|
97
|
+
store.list
|
98
|
+
store.close
|
99
|
+
```
|
100
|
+
|
101
|
+
### Delete certificate
|
102
|
+
|
103
|
+
Deletes a certificate from a certificate store.
|
104
|
+
|
105
|
+
```
|
106
|
+
Input - Certificate thumbprint
|
107
|
+
Return - True/False
|
108
|
+
```
|
109
|
+
|
110
|
+
```
|
111
|
+
Win32::Certstore.open("Root") do |store|
|
112
|
+
store.delete(certificate_thumbprint)
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
or
|
117
|
+
|
118
|
+
```
|
119
|
+
store = Win32::Certstore.open("Root")
|
120
|
+
store.delete(certificate_thumbprint)
|
121
|
+
store.close
|
122
|
+
```
|
123
|
+
|
124
|
+
### Search certificate
|
125
|
+
|
126
|
+
Searches for a certificate in an open certificate store.
|
127
|
+
|
128
|
+
```
|
129
|
+
Input - Search Token as: Comman name, Friendly name, RDN and other attributes
|
130
|
+
Return - Matching certificate list
|
131
|
+
```
|
132
|
+
|
133
|
+
```
|
134
|
+
Win32::Certstore.open("Root") do |store|
|
135
|
+
store.search(search_token)
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
or
|
140
|
+
|
141
|
+
```
|
142
|
+
store = Win32::Certstore.open("Root")
|
143
|
+
store.search(search_token)
|
144
|
+
store.close
|
145
|
+
```
|
146
|
+
|
147
|
+
### Validate certificate
|
148
|
+
|
149
|
+
Validates a certificate in a certificate store on the basis of time validity.
|
150
|
+
|
151
|
+
```
|
152
|
+
Input - Certificate thumbprint
|
153
|
+
Return - True/False
|
154
|
+
|
155
|
+
```
|
156
|
+
|
157
|
+
```
|
158
|
+
Win32::Certstore.open("Root") do |store|
|
159
|
+
store.valid?(certificate_thumbprint)
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
or
|
164
|
+
|
165
|
+
```
|
166
|
+
store = Win32::Certstore.open("Root")
|
167
|
+
store.valid?(certificate_thumbprint)
|
168
|
+
store.close
|
169
|
+
```
|
170
|
+
|
171
|
+
### Performing multiple operations
|
172
|
+
|
173
|
+
To perform more than one operations with single certificate store object
|
174
|
+
|
175
|
+
```
|
176
|
+
raw = File.read "C:\GlobalSignRootCA.pem"
|
177
|
+
certificate_object = OpenSSL::X509::Certificate.new raw
|
178
|
+
|
179
|
+
Win32::Certstore.open('Root') do |store|
|
180
|
+
store.add(certificate_object)
|
181
|
+
store.list
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
or
|
186
|
+
|
187
|
+
```
|
188
|
+
raw = File.read "C:\GlobalSignRootCA.pem"
|
189
|
+
certificate_object = OpenSSL::X509::Certificate.new raw
|
190
|
+
|
191
|
+
store = Win32::Certstore.open('Root')
|
192
|
+
store.add(certificate_object)
|
193
|
+
store.list
|
194
|
+
store.close
|
195
|
+
```
|
196
|
+
|
197
|
+
## Requirements / setup
|
198
|
+
|
199
|
+
### Ruby
|
200
|
+
|
201
|
+
Ruby 1.9.3+ is required.
|
202
|
+
|
203
|
+
## CONTRIBUTING:
|
204
|
+
|
205
|
+
Please file bugs against the WIN32-CERTSTORE project at https://github.com/chef/win32-certstore/issues.
|
206
|
+
|
207
|
+
More information on the contribution process for Chef projects can be found in the [Chef Contributions document](http://docs.chef.io/community_contributions.html).
|
208
|
+
|
209
|
+
# LICENSE:
|
210
|
+
|
211
|
+
Author:: Bryan McLellan (<btm@chef.io>)
|
212
|
+
Copyright:: 2017-2018 Chef Software, Inc.
|
213
|
+
License:: Apache License, Version 2.0
|
214
|
+
|
215
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
216
|
+
you may not use this file except in compliance with the License.
|
217
|
+
You may obtain a copy of the License at
|
218
|
+
|
219
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
220
|
+
|
221
|
+
Unless required by applicable law or agreed to in writing, software
|
222
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
223
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
224
|
+
See the License for the specific language governing permissions and
|
225
|
+
limitations under the License.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
|
3
|
+
# Copyright:: Copyright (c) 2017 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require_relative "win32/certstore"
|
@@ -0,0 +1,129 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
|
3
|
+
# Copyright:: Copyright (c) 2017 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require_relative "certstore/mixin/crypto"
|
19
|
+
require_relative "certstore/mixin/assertions"
|
20
|
+
require_relative "certstore/mixin/helper"
|
21
|
+
require_relative "certstore/mixin/string"
|
22
|
+
require_relative "certstore/store_base"
|
23
|
+
require_relative "certstore/version"
|
24
|
+
|
25
|
+
module Win32
|
26
|
+
class Certstore
|
27
|
+
include Win32::Certstore::Mixin::Crypto
|
28
|
+
extend Win32::Certstore::Mixin::Assertions
|
29
|
+
include Win32::Certstore::Mixin::String
|
30
|
+
include Win32::Certstore::StoreBase
|
31
|
+
|
32
|
+
attr_reader :store_name
|
33
|
+
|
34
|
+
def initialize(store_name)
|
35
|
+
@certstore_handler = open(store_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
# To open given certificate store
|
39
|
+
def self.open(store_name)
|
40
|
+
validate_store(store_name)
|
41
|
+
if block_given?
|
42
|
+
yield new(store_name)
|
43
|
+
else
|
44
|
+
new(store_name)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Adds a new certificate to an open certificate store
|
49
|
+
# @param request [Object] of certificate in OpenSSL::X509::Certificate.new format
|
50
|
+
# @return [true, false] only true or false
|
51
|
+
def add(certificate_obj)
|
52
|
+
cert_add(certstore_handler, certificate_obj)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Return `OpenSSL::X509` certificate object
|
56
|
+
# @param request [thumbprint<string>] of certificate
|
57
|
+
# @return [Object] of certificates in OpenSSL::X509 format
|
58
|
+
def get(certificate_thumbprint)
|
59
|
+
cert_get(certificate_thumbprint)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns all the certificates in a store
|
63
|
+
# @param [nil]
|
64
|
+
# @return [Array] array of certificates list
|
65
|
+
def list
|
66
|
+
cert_list(certstore_handler)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Delete existing certificate from open certificate store
|
70
|
+
# @param request [thumbprint<string>] of certificate
|
71
|
+
# @return [true, false] only true or false
|
72
|
+
def delete(certificate_thumbprint)
|
73
|
+
cert_delete(certstore_handler, certificate_thumbprint)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns all matching certificates in a store
|
77
|
+
# @param request[search_token<string>] attributes of certificates as: CN, RDN, Friendly Name and other attributes
|
78
|
+
# @return [Array] array of certificates list
|
79
|
+
def search(search_token)
|
80
|
+
cert_search(certstore_handler, search_token)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Validates a certificate in a certificate store on the basis of time validity
|
84
|
+
# @param request[thumbprint<string>] of certificate
|
85
|
+
# @return [true, false] only true or false
|
86
|
+
def valid?(certificate_thumbprint)
|
87
|
+
cert_validate(certificate_thumbprint)
|
88
|
+
end
|
89
|
+
|
90
|
+
# To close and destroy pointer of open certificate store handler
|
91
|
+
def close
|
92
|
+
closed = CertCloseStore(@certstore_handler, CERT_CLOSE_STORE_FORCE_FLAG)
|
93
|
+
unless closed
|
94
|
+
last_error = FFI::LastError.error
|
95
|
+
raise SystemCallError.new("Unable to close the Certificate Store.", last_error)
|
96
|
+
end
|
97
|
+
remove_finalizer
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
attr_reader :certstore_handler
|
103
|
+
|
104
|
+
# To open certstore and return open certificate store pointer
|
105
|
+
def open(store_name)
|
106
|
+
certstore_handler = CertOpenSystemStoreW(nil, wstring(store_name))
|
107
|
+
unless certstore_handler
|
108
|
+
last_error = FFI::LastError.error
|
109
|
+
raise SystemCallError.new("Unable to open the Certificate Store `#{store_name}`.", last_error)
|
110
|
+
end
|
111
|
+
add_finalizer(certstore_handler)
|
112
|
+
certstore_handler
|
113
|
+
end
|
114
|
+
|
115
|
+
# Get all open certificate store handler
|
116
|
+
def add_finalizer(certstore_handler)
|
117
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(certstore_handler))
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.finalize(certstore_handler)
|
121
|
+
proc { puts "DESTROY OBJECT #{certstore_handler}" }
|
122
|
+
end
|
123
|
+
|
124
|
+
# To close all open certificate store at the end
|
125
|
+
def remove_finalizer
|
126
|
+
ObjectSpace.undefine_finalizer(self)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Piyush Awasthi (<piyush.awasthi@msystechnologies.com>)
|
3
|
+
# Copyright:: Copyright (c) 2017 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
require "openssl"
|
18
|
+
|
19
|
+
module Win32
|
20
|
+
class Certstore
|
21
|
+
module Mixin
|
22
|
+
module Assertions
|
23
|
+
# Validate certificate store name
|
24
|
+
def validate_store(store_name)
|
25
|
+
unless valid_store_name.include?(store_name&.upcase)
|
26
|
+
raise ArgumentError, "Invalid Certificate Store."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Validate certificate type
|
31
|
+
def validate_certificate(cert_file_path)
|
32
|
+
unless !cert_file_path.nil? && File.extname(cert_file_path) =~ /.cer|.crt|.pfx|.der/
|
33
|
+
raise ArgumentError, "Invalid Certificate format."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Validate certificate Object
|
38
|
+
def validate_certificate_obj(cert_obj)
|
39
|
+
unless cert_obj.class == OpenSSL::X509::Certificate
|
40
|
+
raise ArgumentError, "Invalid Certificate object."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Validate thumbprint
|
45
|
+
def validate_thumbprint(cert_thumbprint)
|
46
|
+
if cert_thumbprint.nil? || cert_thumbprint.strip.empty?
|
47
|
+
raise ArgumentError, "Invalid certificate thumbprint."
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Validate certificate name not nil/empty
|
52
|
+
def validate!(token)
|
53
|
+
raise ArgumentError, "Invalid search token" if !token || token.strip.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Common System call errors
|
57
|
+
def lookup_error(failed_operation = nil)
|
58
|
+
error_no = FFI::LastError.error
|
59
|
+
case error_no
|
60
|
+
when 1223
|
61
|
+
raise SystemCallError.new("The operation was canceled by the user", error_no)
|
62
|
+
when -2146885628
|
63
|
+
raise SystemCallError.new("Cannot find object or property", error_no)
|
64
|
+
when -2146885629
|
65
|
+
raise SystemCallError.new("An error occurred while reading or writing to a file.", error_no)
|
66
|
+
when -2146881269
|
67
|
+
raise SystemCallError.new("ASN1 bad tag value met. -- Is the certificate in DER format?", error_no)
|
68
|
+
when -2146881278
|
69
|
+
raise SystemCallError.new("ASN1 unexpected end of data.", error_no)
|
70
|
+
when -2147024891
|
71
|
+
raise SystemCallError.new("System.UnauthorizedAccessException, Access denied..", error_no)
|
72
|
+
else
|
73
|
+
raise SystemCallError.new("Unable to #{failed_operation} certificate.", error_no)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# These Are Valid certificate store name
|
80
|
+
# CA -> Certification authority certificates.
|
81
|
+
# MY -> A certificate store that holds certificates with associated private keys.
|
82
|
+
# ROOT -> Root certificates.
|
83
|
+
# SPC -> Software Publisher Certificate.
|
84
|
+
def valid_store_name
|
85
|
+
%w{MY CA ROOT SPC}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
|
3
|
+
# Copyright:: Copyright (c) 2017 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require "ffi"
|
20
|
+
|
21
|
+
module Win32
|
22
|
+
class Certstore
|
23
|
+
module Mixin
|
24
|
+
module Crypto
|
25
|
+
extend FFI::Library
|
26
|
+
|
27
|
+
ffi_lib "Crypt32"
|
28
|
+
ffi_convention :stdcall
|
29
|
+
|
30
|
+
# Attempts to use FFI's attach_function method to link a native Win32
|
31
|
+
# function into the calling module. If this fails a dummy method is
|
32
|
+
# defined which when called, raises a helpful exception to the end-user.
|
33
|
+
module FFI::Library
|
34
|
+
def safe_attach_function(win32_func, *args)
|
35
|
+
attach_function(win32_func.to_sym, *args)
|
36
|
+
rescue FFI::NotFoundError
|
37
|
+
define_method(win32_func.to_sym) do |*margs|
|
38
|
+
raise NotImplementedError, "This version of Windows does not implement the Win32 function [#{win32_func}]."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
###############################################
|
44
|
+
# Win32 API Constants
|
45
|
+
###############################################
|
46
|
+
|
47
|
+
CERT_CLOSE_STORE_CHECK_FLAG = 0
|
48
|
+
CERT_CLOSE_STORE_FORCE_FLAG = 1
|
49
|
+
|
50
|
+
# cert encoding flags.
|
51
|
+
CRYPT_ASN_ENCODING = 0x00000001
|
52
|
+
CRYPT_NDR_ENCODING = 0x00000002
|
53
|
+
X509_ASN_ENCODING = 0x00000001
|
54
|
+
X509_NDR_ENCODING = 0x00000002
|
55
|
+
PKCS_7_ASN_ENCODING = 0x00010000
|
56
|
+
PKCS_7_NDR_ENCODING = 0x00020000
|
57
|
+
PKCS_7_OR_X509_ASN_ENCODING = (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)
|
58
|
+
|
59
|
+
# Certificate Display Format
|
60
|
+
CERT_NAME_EMAIL_TYPE = 1
|
61
|
+
CERT_NAME_RDN_TYPE = 2
|
62
|
+
CERT_NAME_ATTR_TYPE = 3
|
63
|
+
CERT_NAME_SIMPLE_DISPLAY_TYPE = 4
|
64
|
+
CERT_NAME_FRIENDLY_DISPLAY_TYPE = 5
|
65
|
+
CERT_NAME_DNS_TYPE = 6
|
66
|
+
CERT_NAME_URL_TYPE = 7
|
67
|
+
CERT_NAME_UPN_TYPE = 8
|
68
|
+
|
69
|
+
# Retrieve Certificates flag
|
70
|
+
CERT_FIND_SUBJECT_STR = 0x00080007
|
71
|
+
CERT_FIND_ISSUER_STR = 0x00080004
|
72
|
+
|
73
|
+
# List Certificates Flag
|
74
|
+
CERT_NAME_ISSUER_FLAG = 0x1
|
75
|
+
CERT_NAME_DISABLE_IE4_UTF8_FLAG = 0x00010000
|
76
|
+
CERT_NAME_SEARCH_ALL_NAMES_FLAG = 0x2
|
77
|
+
CERT_NAME_STR_ENABLE_PUNYCODE_FLAG = 0x00200000
|
78
|
+
|
79
|
+
# Define ffi pointer
|
80
|
+
HCERTSTORE = FFI::TypeDefs[:pointer]
|
81
|
+
HCRYPTPROV_LEGACY = FFI::TypeDefs[:pointer]
|
82
|
+
PCCERT_CONTEXT = FFI::TypeDefs[:pointer]
|
83
|
+
BYTE = FFI::TypeDefs[:pointer]
|
84
|
+
DWORD = FFI::TypeDefs[:uint32]
|
85
|
+
BLOB = FFI::TypeDefs[:ulong]
|
86
|
+
LPSTR = FFI::TypeDefs[:pointer]
|
87
|
+
LPCTSTR = FFI::TypeDefs[:pointer]
|
88
|
+
BOOL = FFI::TypeDefs[:bool]
|
89
|
+
INT_PTR = FFI::TypeDefs[:int]
|
90
|
+
LONG = FFI::TypeDefs[:long]
|
91
|
+
LPVOID = FFI::TypeDefs[:pointer]
|
92
|
+
LPTSTR = FFI::TypeDefs[:pointer]
|
93
|
+
LMSTR = FFI::TypeDefs[:pointer]
|
94
|
+
PWSTR = FFI::TypeDefs[:pointer]
|
95
|
+
LPFILETIME = FFI::TypeDefs[:pointer]
|
96
|
+
PCERT_INFO = FFI::TypeDefs[:pointer]
|
97
|
+
PCTL_USAGE = FFI::TypeDefs[:pointer]
|
98
|
+
PCTL_VERIFY_USAGE_PARA = FFI::TypeDefs[:pointer]
|
99
|
+
PCTL_VERIFY_USAGE_STATUS = FFI::TypeDefs[:pointer]
|
100
|
+
|
101
|
+
class FILETIME < FFI::Struct
|
102
|
+
layout :dwLowDateTime, DWORD,
|
103
|
+
:dwHighDateTime, DWORD
|
104
|
+
end
|
105
|
+
|
106
|
+
class CRYPT_INTEGER_BLOB < FFI::Struct
|
107
|
+
layout :cbData, DWORD, # Count, in bytes, of data
|
108
|
+
:pbData, :pointer # Pointer to data buffer
|
109
|
+
end
|
110
|
+
|
111
|
+
class CRYPT_NAME_BLOB < FFI::Struct
|
112
|
+
layout :cbData, DWORD, # Count, in bytes, of data
|
113
|
+
:pbData, :pointer # Pointer to data buffer
|
114
|
+
def initialize(str = nil)
|
115
|
+
super(nil)
|
116
|
+
if str
|
117
|
+
self[:pbData] = FFI::MemoryPointer.new(2, 128)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class CERT_EXTENSION < FFI::Struct
|
123
|
+
layout :pszObjId, LPTSTR,
|
124
|
+
:fCritical, BOOL,
|
125
|
+
:Value, CRYPT_INTEGER_BLOB
|
126
|
+
end
|
127
|
+
|
128
|
+
class CRYPT_BIT_BLOB < FFI::Struct
|
129
|
+
layout :cbData, DWORD,
|
130
|
+
:pbData, BYTE,
|
131
|
+
:cUnusedBits, DWORD
|
132
|
+
end
|
133
|
+
|
134
|
+
class CRYPT_ALGORITHM_IDENTIFIER < FFI::Struct
|
135
|
+
layout :pszObjId, LPSTR,
|
136
|
+
:Parameters, CRYPT_INTEGER_BLOB
|
137
|
+
end
|
138
|
+
|
139
|
+
class CERT_PUBLIC_KEY_INFO < FFI::Struct
|
140
|
+
layout :Algorithm, CRYPT_ALGORITHM_IDENTIFIER,
|
141
|
+
:PublicKey, CRYPT_BIT_BLOB
|
142
|
+
end
|
143
|
+
|
144
|
+
class CERT_INFO < FFI::Struct
|
145
|
+
layout :dwVersion, DWORD,
|
146
|
+
:SerialNumber, CRYPT_INTEGER_BLOB,
|
147
|
+
:SignatureAlgorithm, CRYPT_ALGORITHM_IDENTIFIER,
|
148
|
+
:Issuer, CRYPT_NAME_BLOB,
|
149
|
+
:NotBefore, FILETIME,
|
150
|
+
:NotAfter, FILETIME,
|
151
|
+
:Subject, CRYPT_NAME_BLOB,
|
152
|
+
:SubjectPublicKeyInfo, CERT_PUBLIC_KEY_INFO,
|
153
|
+
:IssuerUniqueId, CRYPT_BIT_BLOB,
|
154
|
+
:SubjectUniqueId, CRYPT_BIT_BLOB,
|
155
|
+
:cExtension, DWORD,
|
156
|
+
:rgExtension, CERT_EXTENSION
|
157
|
+
end
|
158
|
+
|
159
|
+
class CERT_CONTEXT < FFI::Struct
|
160
|
+
layout :dwCertEncodingType, DWORD,
|
161
|
+
:pbCertEncoded, BYTE,
|
162
|
+
:cbCertEncoded, DWORD,
|
163
|
+
:pCertInfo, CERT_INFO,
|
164
|
+
:hCertStore, HCERTSTORE
|
165
|
+
end
|
166
|
+
|
167
|
+
###############################################################################
|
168
|
+
# Windows Function
|
169
|
+
# To know description about below windows function
|
170
|
+
# Search Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa376560
|
171
|
+
###############################################################################
|
172
|
+
|
173
|
+
# To opens the most common system certificate store
|
174
|
+
safe_attach_function :CertOpenSystemStoreW, [HCRYPTPROV_LEGACY, LPCTSTR], HCERTSTORE
|
175
|
+
# To close the already open certificate store
|
176
|
+
safe_attach_function :CertCloseStore, [HCERTSTORE, DWORD], BOOL
|
177
|
+
# To create encoded certificate context
|
178
|
+
safe_attach_function :CertCreateCertificateContext, [DWORD, BYTE, DWORD], PCCERT_CONTEXT
|
179
|
+
# To retrieves certificates in a certificate store
|
180
|
+
safe_attach_function :CertEnumCertificatesInStore, [HCERTSTORE, PCCERT_CONTEXT], PCCERT_CONTEXT
|
181
|
+
# To get certificate name
|
182
|
+
safe_attach_function :CertGetNameStringW, [PCCERT_CONTEXT, DWORD, DWORD, LPVOID, LPTSTR, DWORD], DWORD
|
183
|
+
# To find all of the property identifiers for the specified certificate.
|
184
|
+
safe_attach_function :CertEnumCertificateContextProperties, [PCCERT_CONTEXT, DWORD], DWORD
|
185
|
+
# Clean up
|
186
|
+
safe_attach_function :CertFreeCertificateContext, [PCCERT_CONTEXT], BOOL
|
187
|
+
# Add certificate file in certificate store.
|
188
|
+
safe_attach_function :CertAddSerializedElementToStore, [HCERTSTORE, :pointer, DWORD, DWORD, DWORD, DWORD, LMSTR, LPVOID], BOOL
|
189
|
+
# Add certification to certification store - Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa376015(v=vs.85).aspx
|
190
|
+
safe_attach_function :CertAddEncodedCertificateToStore, [HCERTSTORE, DWORD, PWSTR, DWORD, INT_PTR, PCCERT_CONTEXT], BOOL
|
191
|
+
safe_attach_function :CertSerializeCertificateStoreElement, [PCCERT_CONTEXT, DWORD, :pointer, DWORD], BOOL
|
192
|
+
# Duplicates a certificate context by incrementing its reference count
|
193
|
+
safe_attach_function :CertDuplicateCertificateContext, [PCCERT_CONTEXT], PCCERT_CONTEXT
|
194
|
+
# Delete certification from certification store
|
195
|
+
safe_attach_function :CertDeleteCertificateFromStore, [PCCERT_CONTEXT], BOOL
|
196
|
+
# To retrieve specific certificates from certificate store
|
197
|
+
safe_attach_function :CertFindCertificateInStore, [HCERTSTORE, DWORD, DWORD, DWORD, LPVOID, PCCERT_CONTEXT], PCCERT_CONTEXT
|
198
|
+
|
199
|
+
safe_attach_function :PFXExportCertStoreEx, [HCERTSTORE, CRYPT_INTEGER_BLOB, LPCTSTR, LPVOID, DWORD], BOOL
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Piyush Awasthi (<piyush.awasthi@msystechnologies.com>)
|
3
|
+
# Copyright:: Copyright (c) 2018 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require 'date'
|
19
|
+
|
20
|
+
module Win32
|
21
|
+
class Certstore
|
22
|
+
module Mixin
|
23
|
+
module Helper
|
24
|
+
|
25
|
+
# PSCommand to search certificate from thumbprint and convert in pem
|
26
|
+
def cert_ps_cmd(thumbprint)
|
27
|
+
<<-EOH
|
28
|
+
$content = $null
|
29
|
+
$cert = Get-ChildItem Cert:\ -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
|
+
)
|
37
|
+
}
|
38
|
+
$content
|
39
|
+
EOH
|
40
|
+
end
|
41
|
+
|
42
|
+
# validate certificate not_before and not_after date in UTC
|
43
|
+
def valid_duration?(cert_obj)
|
44
|
+
cert_obj.not_before < Time.now.utc && cert_obj.not_after > Time.now.utc
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Daniel DeLeo (<dan@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2017 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require "mixlib/shellout"
|
19
|
+
|
20
|
+
module Win32
|
21
|
+
class Certstore
|
22
|
+
module Mixin
|
23
|
+
module ShellOut
|
24
|
+
def shell_out_command(*command_args)
|
25
|
+
cmd = Mixlib::ShellOut.new(*command_args)
|
26
|
+
cmd.live_stream
|
27
|
+
cmd.run_command
|
28
|
+
if cmd.error!
|
29
|
+
raise Mixlib::ShellOut::ShellCommandFailed, cmd.error!
|
30
|
+
end
|
31
|
+
cmd
|
32
|
+
end
|
33
|
+
# Run a command under powershell with the same API as shell_out. The
|
34
|
+
# options hash is extended to take an "architecture" flag which
|
35
|
+
# can be set to :i386 or :x86_64 to force the windows architecture.
|
36
|
+
#
|
37
|
+
# @param script [String] script to run
|
38
|
+
# @param options [Hash] options hash
|
39
|
+
# @return [Mixlib::Shellout] mixlib-shellout object
|
40
|
+
def powershell_out(*command_args)
|
41
|
+
script = command_args.first
|
42
|
+
options = command_args.last.is_a?(Hash) ? command_args.last : nil
|
43
|
+
|
44
|
+
run_command_with_os_architecture(script, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Run a command under powershell with the same API as shell_out!
|
48
|
+
# (raises exceptions on errors)
|
49
|
+
#
|
50
|
+
# @param script [String] script to run
|
51
|
+
# @param options [Hash] options hash
|
52
|
+
# @return [Mixlib::Shellout] mixlib-shellout object
|
53
|
+
def powershell_out!(*command_args)
|
54
|
+
cmd = powershell_out(*command_args)
|
55
|
+
cmd.error!
|
56
|
+
cmd
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Helper function to run shell_out and wrap it with the correct
|
62
|
+
# flags to possibly disable WOW64 redirection (which we often need
|
63
|
+
# because chef-client runs as a 32-bit app on 64-bit windows).
|
64
|
+
#
|
65
|
+
# @param script [String] script to run
|
66
|
+
# @param options [Hash] options hash
|
67
|
+
# @return [Mixlib::Shellout] mixlib-shellout object
|
68
|
+
def run_command_with_os_architecture(script, options)
|
69
|
+
options ||= {}
|
70
|
+
options = options.dup
|
71
|
+
arch = options.delete(:architecture)
|
72
|
+
|
73
|
+
shell_out_command(
|
74
|
+
build_powershell_command(script),
|
75
|
+
options
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Helper to build a powershell command around the script to run.
|
80
|
+
#
|
81
|
+
# @param script [String] script to run
|
82
|
+
# @return [String] powershell command to execute
|
83
|
+
def build_powershell_command(script)
|
84
|
+
flags = [
|
85
|
+
# Hides the copyright banner at startup.
|
86
|
+
"-NoLogo",
|
87
|
+
# Does not present an interactive prompt to the user.
|
88
|
+
"-NonInteractive",
|
89
|
+
# Does not load the Windows PowerShell profile.
|
90
|
+
"-NoProfile",
|
91
|
+
# always set the ExecutionPolicy flag
|
92
|
+
# see http://technet.microsoft.com/en-us/library/ee176961.aspx
|
93
|
+
"-ExecutionPolicy Unrestricted",
|
94
|
+
# Powershell will hang if STDIN is redirected
|
95
|
+
# http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected
|
96
|
+
"-InputFormat None",
|
97
|
+
]
|
98
|
+
|
99
|
+
"powershell.exe #{flags.join(' ')} -Command \"#{script.gsub('"', '\"')}\""
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Jay Mundrawala(<jdm@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2017 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
module Win32
|
19
|
+
class Certstore
|
20
|
+
module Mixin
|
21
|
+
module String
|
22
|
+
def wstring(str)
|
23
|
+
if str.nil? || str.encoding == Encoding::UTF_16LE
|
24
|
+
str
|
25
|
+
else
|
26
|
+
utf8_to_wide(str)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def utf8_to_wide(ustring)
|
31
|
+
# ensure it is actually UTF-8
|
32
|
+
# Ruby likes to mark binary data as ASCII-8BIT
|
33
|
+
ustring = (ustring + "").force_encoding("UTF-8") if ustring.respond_to?(:force_encoding) && ustring.encoding.name != "UTF-8"
|
34
|
+
|
35
|
+
# ensure we have the double-null termination Windows Wide likes
|
36
|
+
ustring += "\000\000" if ustring.length == 0 || ustring[-1].chr != "\000"
|
37
|
+
|
38
|
+
# encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode
|
39
|
+
ustring = begin
|
40
|
+
if ustring.respond_to?(:encode)
|
41
|
+
ustring.encode("UTF-16LE")
|
42
|
+
else
|
43
|
+
require "iconv"
|
44
|
+
Iconv.conv("UTF-16LE", "UTF-8", ustring)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
ustring
|
48
|
+
end
|
49
|
+
|
50
|
+
def wide_to_utf8(wstring)
|
51
|
+
# ensure it is actually UTF-16LE
|
52
|
+
# Ruby likes to mark binary data as ASCII-8BIT
|
53
|
+
wstring = wstring.force_encoding("UTF-16LE") if wstring.respond_to?(:force_encoding)
|
54
|
+
|
55
|
+
# encode it all as UTF-8
|
56
|
+
wstring = begin
|
57
|
+
if wstring.respond_to?(:encode)
|
58
|
+
wstring.encode("UTF-8")
|
59
|
+
else
|
60
|
+
require "iconv"
|
61
|
+
Iconv.conv("UTF-8", "UTF-16LE", wstring)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# remove trailing CRLF and NULL characters
|
65
|
+
wstring.strip!
|
66
|
+
wstring
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
#
|
2
|
+
# Author:: John Keiser (<jkeiser@chef.io>)
|
3
|
+
# Author:: Seth Chisamore (<schisamo@chef.io>)
|
4
|
+
# Copyright:: Copyright (c) 2017 Chef Software, Inc.
|
5
|
+
# License:: Apache License, Version 2.0
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
require_relative "string"
|
20
|
+
|
21
|
+
module Win32::Certstore::Mixin::Unicode
|
22
|
+
end
|
23
|
+
|
24
|
+
module FFI
|
25
|
+
class Pointer
|
26
|
+
include Win32::Certstore::Mixin::String
|
27
|
+
def read_wstring(num_wchars = nil)
|
28
|
+
if num_wchars.nil?
|
29
|
+
# Find the length of the string
|
30
|
+
length = 0
|
31
|
+
last_char = nil
|
32
|
+
while last_char != "\000\000"
|
33
|
+
length += 1
|
34
|
+
last_char = get_bytes(0, length * 2)[-2..-1]
|
35
|
+
end
|
36
|
+
|
37
|
+
num_wchars = length
|
38
|
+
end
|
39
|
+
wide_to_utf8(get_bytes(0, num_wchars * 2))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class String
|
45
|
+
include Win32::Certstore::Mixin::String
|
46
|
+
|
47
|
+
def to_wstring
|
48
|
+
utf8_to_wide(self)
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Piyush Awasthi (<piyush.awasthi@msystechnologies.com>)
|
3
|
+
# Copyright:: Copyright (c) 2017 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require_relative "mixin/crypto"
|
19
|
+
require_relative "mixin/string"
|
20
|
+
require_relative "mixin/shell_out"
|
21
|
+
require_relative "mixin/unicode"
|
22
|
+
require "openssl"
|
23
|
+
require "json"
|
24
|
+
|
25
|
+
module Win32
|
26
|
+
class Certstore
|
27
|
+
module StoreBase
|
28
|
+
include Win32::Certstore::Mixin::Crypto
|
29
|
+
include Win32::Certstore::Mixin::Assertions
|
30
|
+
include Win32::Certstore::Mixin::String
|
31
|
+
include Win32::Certstore::Mixin::ShellOut
|
32
|
+
include Win32::Certstore::Mixin::Unicode
|
33
|
+
include Win32::Certstore::Mixin::Helper
|
34
|
+
|
35
|
+
# Adding new certification in open certificate and return boolean
|
36
|
+
# store_handler => Open certificate store handler
|
37
|
+
# certificate_obj => certificate object must be in OpenSSL::X509
|
38
|
+
def cert_add(store_handler, certificate_obj)
|
39
|
+
validate_certificate_obj(certificate_obj)
|
40
|
+
begin
|
41
|
+
cert_args = cert_add_args(store_handler, certificate_obj)
|
42
|
+
if CertAddEncodedCertificateToStore(*cert_args)
|
43
|
+
true
|
44
|
+
else
|
45
|
+
lookup_error
|
46
|
+
end
|
47
|
+
rescue Exception => e
|
48
|
+
lookup_error("add")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get certificate from open certificate store and return certificate object
|
53
|
+
# store_handler => Open certificate store handler
|
54
|
+
# certificate_thumbprint => thumbprint is a hash. which could be sha1 or md5.
|
55
|
+
def cert_get(certificate_thumbprint)
|
56
|
+
validate_thumbprint(certificate_thumbprint)
|
57
|
+
thumbprint = update_thumbprint(certificate_thumbprint)
|
58
|
+
cert_pem = get_cert_pem(thumbprint)
|
59
|
+
cert_pem = format_pem(cert_pem)
|
60
|
+
unless cert_pem.empty?
|
61
|
+
build_openssl_obj(cert_pem)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Listing certificate of open certstore and return list in json
|
66
|
+
def cert_list(store_handler)
|
67
|
+
cert_name = memory_ptr
|
68
|
+
cert_list = []
|
69
|
+
begin
|
70
|
+
while (pcert_context = CertEnumCertificatesInStore(store_handler, pcert_context)) && (not pcert_context.null?) do
|
71
|
+
cert_args = cert_get_name_args(pcert_context, cert_name, CERT_NAME_FRIENDLY_DISPLAY_TYPE)
|
72
|
+
if CertGetNameStringW(*cert_args)
|
73
|
+
cert_list << cert_name.read_wstring
|
74
|
+
end
|
75
|
+
end
|
76
|
+
CertFreeCertificateContext(pcert_context)
|
77
|
+
rescue Exception => e
|
78
|
+
lookup_error("list")
|
79
|
+
end
|
80
|
+
cert_list.to_json
|
81
|
+
end
|
82
|
+
|
83
|
+
# Deleting certificate from open certificate store and return boolean
|
84
|
+
# store_handler => Open certificate store handler
|
85
|
+
# certificate_thumbprint => thumbprint is a hash. which could be sha1 or md5.
|
86
|
+
def cert_delete(store_handler, certificate_thumbprint)
|
87
|
+
validate_thumbprint(certificate_thumbprint)
|
88
|
+
cert_name = memory_ptr
|
89
|
+
thumbprint = update_thumbprint(certificate_thumbprint)
|
90
|
+
cert_pem = format_pem(get_cert_pem(thumbprint))
|
91
|
+
cert_rdn = get_rdn(build_openssl_obj(cert_pem))
|
92
|
+
cert_delete_flag = false
|
93
|
+
begin
|
94
|
+
cert_args = cert_find_args(store_handler, cert_rdn)
|
95
|
+
if (pcert_context = CertFindCertificateInStore(*cert_args) and !pcert_context.null?)
|
96
|
+
cert_delete_flag = CertDeleteCertificateFromStore(CertDuplicateCertificateContext(pcert_context)) || lookup_error
|
97
|
+
end
|
98
|
+
CertFreeCertificateContext(pcert_context)
|
99
|
+
rescue Exception => e
|
100
|
+
lookup_error("delete")
|
101
|
+
end
|
102
|
+
cert_delete_flag
|
103
|
+
end
|
104
|
+
|
105
|
+
# Verify certificate from open certificate store and return boolean or exceptions
|
106
|
+
# store_handler => Open certificate store handler
|
107
|
+
# certificate_thumbprint => thumbprint is a hash. which could be sha1 or md5.
|
108
|
+
def cert_validate(certificate_thumbprint)
|
109
|
+
validate_thumbprint(certificate_thumbprint)
|
110
|
+
thumbprint = update_thumbprint(certificate_thumbprint)
|
111
|
+
cert_pem = get_cert_pem(thumbprint)
|
112
|
+
cert_pem = format_pem(cert_pem)
|
113
|
+
verify_certificate(cert_pem)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Search certificate from open certificate store and return list
|
117
|
+
# store_handler => Open certificate store handler
|
118
|
+
# search_token => CN, RDN or any certificate attribute
|
119
|
+
def cert_search(store_handler, search_token)
|
120
|
+
raise ArgumentError, "Invalid search token" if !search_token || search_token.strip.empty?
|
121
|
+
cert_rdn = memory_ptr
|
122
|
+
certificate_list =[]
|
123
|
+
counter = 0
|
124
|
+
begin
|
125
|
+
while (pcert_context = CertEnumCertificatesInStore(store_handler, pcert_context) and !pcert_context.null?)
|
126
|
+
cert_property = get_cert_property(pcert_context)
|
127
|
+
if cert_property.include?(search_token)
|
128
|
+
certificate_list << [cert_property[CERT_NAME_FRIENDLY_DISPLAY_TYPE], cert_property[CERT_NAME_RDN_TYPE]]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
CertFreeCertificateContext(pcert_context)
|
132
|
+
rescue Exception => e
|
133
|
+
lookup_error
|
134
|
+
end
|
135
|
+
certificate_list
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
# Build arguments for CertAddEncodedCertificateToStore
|
141
|
+
def cert_add_args(store_handler, certificate_obj)
|
142
|
+
[store_handler, X509_ASN_ENCODING, der_cert(certificate_obj), certificate_obj.to_s.bytesize, 2, nil]
|
143
|
+
end
|
144
|
+
|
145
|
+
# Build arguments for CertFindCertificateInStore
|
146
|
+
def cert_find_args(store_handler, cert_rdn)
|
147
|
+
[store_handler, X509_ASN_ENCODING, 0, CERT_FIND_ISSUER_STR, cert_rdn.to_wstring, nil]
|
148
|
+
end
|
149
|
+
|
150
|
+
# Match certificate CN exist in cert_rdn
|
151
|
+
def is_cn_match?(cert_rdn, certificate_name)
|
152
|
+
cert_rdn.read_wstring.match(/(^|\W)#{certificate_name}($|\W)/i)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Get Certificate all properties
|
156
|
+
def get_cert_property(pcert_context)
|
157
|
+
property_value = memory_ptr
|
158
|
+
property_list = []
|
159
|
+
property_list[0] = ""
|
160
|
+
(1..8).to_a.each do |property_type|
|
161
|
+
CertGetNameStringW(pcert_context, property_type, CERT_NAME_ISSUER_FLAG, nil, property_value, 1024)
|
162
|
+
property_list << property_value.read_wstring
|
163
|
+
end
|
164
|
+
property_list
|
165
|
+
end
|
166
|
+
|
167
|
+
# Build argument for CertGetNameStringW
|
168
|
+
def cert_get_name_args(pcert_context, cert_name, search_type)
|
169
|
+
[pcert_context, search_type, CERT_NAME_ISSUER_FLAG, nil, cert_name, 1024]
|
170
|
+
end
|
171
|
+
|
172
|
+
# Remove extra space and : from thumbprint
|
173
|
+
def update_thumbprint(certificate_thumbprint)
|
174
|
+
certificate_thumbprint.gsub(/[^A-Za-z0-9]/, '')
|
175
|
+
end
|
176
|
+
|
177
|
+
# Verify OpenSSL::X509::Certificate object
|
178
|
+
def verify_certificate(cert_pem)
|
179
|
+
return "Certificate not found" if cert_pem.empty?
|
180
|
+
valid_duration?(build_openssl_obj(cert_pem))
|
181
|
+
end
|
182
|
+
|
183
|
+
# Convert OpenSSL::X509::Certificate object in .der formate
|
184
|
+
def der_cert(cert_obj)
|
185
|
+
FFI::MemoryPointer.from_string(cert_obj.to_der)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Get certificate pem
|
189
|
+
def get_cert_pem(thumbprint)
|
190
|
+
get_data = powershell_out!(cert_ps_cmd(thumbprint))
|
191
|
+
get_data.stdout
|
192
|
+
end
|
193
|
+
|
194
|
+
# To get RDN from certificate object
|
195
|
+
def get_rdn(cert_obj)
|
196
|
+
cert_obj.issuer.to_s.concat("/").scan(/=(.*?)\//).join(", ")
|
197
|
+
end
|
198
|
+
|
199
|
+
# Format pem
|
200
|
+
def format_pem(cert_pem)
|
201
|
+
cert_pem.delete("\r")
|
202
|
+
end
|
203
|
+
|
204
|
+
# Build pem to OpenSSL::X509::Certificate object
|
205
|
+
def build_openssl_obj(cert_pem)
|
206
|
+
OpenSSL::X509::Certificate.new(cert_pem)
|
207
|
+
end
|
208
|
+
# Create empty memory pointer
|
209
|
+
def memory_ptr
|
210
|
+
FFI::MemoryPointer.new(2, 256)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: win32-certstore
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- nimisha
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mixlib-shellout
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: ffi
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- nimisha.sharad@msystechnologies.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- README.md
|
91
|
+
- lib/win32-certstore.rb
|
92
|
+
- lib/win32/certstore.rb
|
93
|
+
- lib/win32/certstore/mixin/assertions.rb
|
94
|
+
- lib/win32/certstore/mixin/crypto.rb
|
95
|
+
- lib/win32/certstore/mixin/helper.rb
|
96
|
+
- lib/win32/certstore/mixin/shell_out.rb
|
97
|
+
- lib/win32/certstore/mixin/string.rb
|
98
|
+
- lib/win32/certstore/mixin/unicode.rb
|
99
|
+
- lib/win32/certstore/store_base.rb
|
100
|
+
- lib/win32/certstore/version.rb
|
101
|
+
homepage: https://github.com/chef/win32-certstore
|
102
|
+
licenses: []
|
103
|
+
metadata:
|
104
|
+
yard.run: yri
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.6.14
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: Ruby library for accessing the certificate store on Windows.
|
125
|
+
test_files: []
|