diff --git a/lib/win32/certstore.rb b/lib/win32/certstore.rb index fc5b139..544dcca 100644 --- a/lib/win32/certstore.rb +++ b/lib/win32/certstore.rb @@ -70,6 +70,11 @@ def search(certificate_name) cert_search(certstore_handler, certificate_name) end + # Validate certificate from open certificate store and return boolean + def verify(certificate_name) + cert_verify(certstore_handler, certificate_name) + end + # To close and destroy pointer of open certificate store handler def close closed = CertCloseStore(@certstore_handler, CERT_CLOSE_STORE_FORCE_FLAG) diff --git a/lib/win32/certstore/mixin/helper.rb b/lib/win32/certstore/mixin/helper.rb index 6f4b37f..ace7561 100644 --- a/lib/win32/certstore/mixin/helper.rb +++ b/lib/win32/certstore/mixin/helper.rb @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +require 'date' + module Win32 class Certstore module Mixin @@ -35,6 +37,11 @@ def cert_ps_cmd(thumbprint) } $content" end + + # validate certificate not_before and not_after date in UTC + def valid_duration(cert_obj) + cert_obj.not_before < Time.now.utc && cert_obj.not_after > Time.now.utc + end end end end diff --git a/lib/win32/certstore/store_base.rb b/lib/win32/certstore/store_base.rb index ca06dd8..a191d6c 100644 --- a/lib/win32/certstore/store_base.rb +++ b/lib/win32/certstore/store_base.rb @@ -99,6 +99,17 @@ def cert_delete(store_handler, certificate_thumbprint) end end + # Verify certificate from open certificate store and return boolean or exceptions + # store_handler => Open certificate store handler + # certificate_thumbprint => thumbprint is a hash. which could be sha1 or md5. + def cert_verify(store_handler, certificate_thumbprint) + validate_thumbprint(certificate_thumbprint) + thumbprint = update_thumbprint(certificate_thumbprint) + cert_pem = get_cert_pem(thumbprint) + cert_pem = format_pem(cert_pem) + verify_certificate(cert_pem) + end + private # Build arguments for CertAddEncodedCertificateToStore @@ -116,6 +127,12 @@ def update_thumbprint(certificate_thumbprint) certificate_thumbprint.gsub(/[^A-Za-z0-9]/, '') end + # Verify OpenSSL::X509::Certificate object + def verify_certificate(cert_pem) + return "Certificate not found" if cert_pem.empty? + valid_duration(build_openssl_obj(cert_pem)) + end + # Convert OpenSSL::X509::Certificate object in .der formate def der_cert(cert_obj) FFI::MemoryPointer.from_string(cert_obj.to_der) diff --git a/spec/win32/unit/certstore_spec.rb b/spec/win32/unit/certstore_spec.rb index d24d5f6..d6ea9e1 100644 --- a/spec/win32/unit/certstore_spec.rb +++ b/spec/win32/unit/certstore_spec.rb @@ -184,7 +184,6 @@ expect(cert_obj.not_after.to_s).to eql("2028-01-28 12:00:00 UTC") end end - end describe "Perform more than one operations with single certstore object" do @@ -208,6 +207,88 @@ end end + describe "#cert_verify" do + context "When passing empty certificate store name" do + let (:store_name) { "" } + it "raises ArgumentError" do + expect { certstore.open(store_name) }.to raise_error("Invalid Certificate Store.") + end + end + + context "When passing empty thumbprint" do + let (:store_name) { "root" } + let (:thumbprint) { " " } + it "raises ArgumentError" do + store = certstore.open(store_name) + expect { store.verify(thumbprint) }.to raise_error("Invalid certificate thumbprint.") + end + end + + context "When passing thumbprint is nil" do + let (:store_name) { "root" } + let (:thumbprint) { nil } + it "raises ArgumentError" do + store = certstore.open(store_name) + expect { store.verify(thumbprint) }.to raise_error("Invalid certificate thumbprint.") + end + end + + context "When passing invalid thumbprint" do + let (:store_name) { "root" } + let (:thumbprint) { "b1bc968bd4f49d622aa89a81f2150152a41d829c" } + before(:each) do + allow_any_instance_of(certbase).to receive(:get_cert_pem).and_return([]) + end + it "returns Certificate not found" do + store = certstore.open(store_name) + cert_obj = store.verify(thumbprint) + expect(cert_obj).to eql("Certificate not found") + end + end + + context "When passing valid thumbprint" do + let (:store_name) { "root" } + let (:thumbprint) { "b1bc968bd4f49d622aa89a81f2150152a41d829909c" } + let (:cert_pem) { File.read('.\spec\win32\unit\assets\GlobalSignRootCA.pem') } + before(:each) do + allow_any_instance_of(certbase).to receive(:get_cert_pem).and_return([cert_pem]) + end + it "returns OpenSSL::X509::Certificate Object" do + store = certstore.open(store_name) + cert_obj = store.verify(thumbprint) + expect(cert_obj).to eql(true) + end + end + + context "When passing valid thumbprint with spaces" do + let (:store_name) { "root" } + let (:thumbprint) { "b1 bc 96 8b d4 f4 9d 62 2a a8 9a 81 f2 15 01 52 a4 1d 82 9c" } + let (:cert_pem) { File.read('.\spec\win32\unit\assets\GlobalSignRootCA.pem') } + before(:each) do + allow_any_instance_of(certbase).to receive(:get_cert_pem).and_return([cert_pem]) + end + it "returns OpenSSL::X509::Certificate Object" do + store = certstore.open(store_name) + cert_obj = store.verify(thumbprint) + expect(cert_obj).to eql(true) + end + end + + context "When passing valid thumbprint with :" do + let (:store_name) { "root" } + let (:thumbprint) { "b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c" } + let (:cert_pem) { File.read('.\spec\win32\unit\assets\GlobalSignRootCA.pem') } + before(:each) do + allow_any_instance_of(certbase).to receive(:get_cert_pem).and_return([cert_pem]) + end + it "returns OpenSSL::X509::Certificate Object" do + store = certstore.open(store_name) + cert_obj = store.verify(thumbprint) + expect(cert_obj).to eql(true) + end + end + end + describe "#Failed with FFI::LastError" do context "While adding or deleting or retrieving certificate" do let (:store_name) { "root" }