win32-certstore 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|