Skip to content

Commit c2c39c9

Browse files
committed
fix: Refacto win32 keystore usage with win32 API
1 parent 3de8a77 commit c2c39c9

File tree

3 files changed

+166
-80
lines changed

3 files changed

+166
-80
lines changed

lib/GLPI/Agent/HTTP/Client.pm

Lines changed: 17 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -630,89 +630,26 @@ sub _KeyChain_or_KeyStore_Export {
630630
@certs = IO::Socket::SSL::Utils::PEM_file2certs($file)
631631
if -s $file;
632632
} else {
633-
my @certCommands;
634-
if ($self->{ssl_keystore}) {
635-
foreach my $case (split(/,+/, $self->{ssl_keystore})) {
636-
$case = trimWhitespace($case);
637-
if ($case =~ /^(Service|Enterprise|GroupPolicy|User)?-?(My|CA|Root)$/) {
638-
my $store = $2 =~ /CA/i ? "CA" : "Root";
639-
my $option = $1 ? " -$1" : "";
640-
push @certCommands, "certutil -Silent -Split$option -Store $store";
641-
} else {
642-
$logger->debug("Unsupported ssl-keystore option definition: $case");
643-
}
644-
}
633+
GLPI::Agent::Tools::Win32::KeyStore->use();
634+
if ($EVAL_ERROR) {
635+
$logger->debug("Failed to load KeyStore support: $EVAL_ERROR");
645636
} else {
646-
@certCommands = (
647-
"certutil -Silent -Split -Store CA",
648-
"certutil -Silent -Split -Store Root",
649-
"certutil -Silent -Split -Enterprise -Store CA",
650-
"certutil -Silent -Split -Enterprise -Store Root",
651-
"certutil -Silent -Split -GroupPolicy -Store CA",
652-
"certutil -Silent -Split -GroupPolicy -Store Root",
653-
"certutil -Silent -Split -User -Store CA",
654-
"certutil -Silent -Split -User -Store Root"
655-
);
656-
}
657-
658-
unless (@certCommands) {
659-
$logger->debug("No keystore to export server certificates from");
660-
return
661-
}
662-
663-
# Windows keystore support
664-
Cwd->require();
665-
my $cwd = Cwd::cwd();
666-
667-
# Create a temporary folder in vardir to cd & export certificates
668-
my $tmpdir = File::Temp->newdir(
669-
TEMPLATE => "$basename-export-XXXXXX",
670-
DIR => $vardir,
671-
TMPDIR => 1,
672-
);
673-
my $certdir = $tmpdir->dirname;
674-
$certdir =~ s{\\}{/}g;
675-
if (-d $certdir) {
676-
$logger->debug2("Changing to '$certdir' temporary folder");
677-
chdir $certdir;
678-
679-
my @deletefolder;
680-
foreach my $command (@certCommands) {
681-
my ($kind, $store) = $command =~ /-Split( -\w+)? -Store (\w+)$/;
682-
my $storeDirname = $kind && $kind =~ /^ -(\w+)$/ ? "$1-$store" : $store;
683-
mkdir $storeDirname;
684-
chdir $storeDirname;
685-
getAllLines(
686-
command => $command,
687-
logger => $logger
688-
);
689-
chdir "..";
690-
push @deletefolder, $storeDirname;
691-
}
692-
693-
# Export certificates from keystore as crt files
694-
695-
# Convert each crt file to base64 encoded cer file and concatenate in certchain file
696-
File::Glob->require();
697-
foreach my $certfile (File::Glob::bsd_glob("$certdir/*/*")) {
698-
if ($certfile =~ m{/([^/]+/[^/]+\.crt)$}) {
699-
getAllLines(
700-
command => "certutil -encode $1 temp.cer",
701-
logger => $logger
702-
);
703-
push @certs, IO::Socket::SSL::Utils::PEM_file2cert("$certdir/temp.cer")
704-
if -s "$certdir/temp.cer";
705-
unlink "$certdir/temp.cer";
637+
GLPI::Agent::Tools::Win32::KeyStore->import("getKeyStore");
638+
if ($self->{ssl_keystore}) {
639+
foreach my $case (split(/,+/, $self->{ssl_keystore})) {
640+
$case = uc(trimWhitespace($case));
641+
if ($case =~ /^(CA|ROOT|TRUST|MY)$/) {
642+
push @certs, getKeyStore(
643+
logger => $logger,
644+
store => $case
645+
);
646+
} else {
647+
$logger->debug("Unsupported ssl-keystore option definition: $case");
648+
}
706649
}
707-
unlink $certfile;
708650
}
709-
710-
# Cleanup temp subfolders
711-
map { rmdir $_ } @deletefolder;
712-
713-
# Get back to current dir
714-
$logger->debug2("Changing back to '$cwd' folder");
715-
chdir $cwd;
651+
push @certs, getKeyStore(logger => $logger)
652+
unless @certs;
716653
}
717654
}
718655

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package GLPI::Agent::Tools::Win32::KeyStore;
2+
3+
use strict;
4+
use warnings;
5+
6+
use parent 'Exporter';
7+
8+
use UNIVERSAL::require();
9+
use English qw(-no_match_vars);
10+
11+
BEGIN {
12+
# Only set if you're a developer and need to debug Win32::API usage
13+
$Win32::API::DEBUG = 0;
14+
}
15+
16+
use Net::SSLeay;
17+
18+
Win32::API->require();
19+
20+
use GLPI::Agent::Tools;
21+
22+
use constant CERT_NAME_SIMPLE_DISPLAY_TYPE => 4;
23+
use constant X509_ASN_ENCODING => 1;
24+
25+
use constant _log_prefix => "[ssl-keystore] ";
26+
27+
our @EXPORT = qw(
28+
getKeyStore
29+
);
30+
31+
Win32::API::Type->typedef('HCERTSTORE', 'ULONG*');
32+
Win32::API::Type->typedef('HCRYPTPROV_LEGACY', 'ULONG*');
33+
34+
my $_CertOpenSystemStoreA = Win32::API::More->Import(
35+
crypt32 => qq{
36+
HCERTSTORE CertOpenSystemStoreA(
37+
HCRYPTPROV_LEGACY hProv,
38+
LPCSTR szSubSystemProtocol
39+
);
40+
}
41+
);
42+
43+
my $CertCloseStore = Win32::API::More->Import(
44+
crypt32 => qq{
45+
BOOL CertCloseStore(
46+
HCERTSTORE hCertStore,
47+
DWORD dwFlags
48+
);
49+
}
50+
);
51+
52+
my $_CertEnumCertificatesInStore = Win32::API::More->Import(
53+
crypt32 => 'CertEnumCertificatesInStore', 'NN', 'N'
54+
);
55+
56+
my $CertGetNameStringA = Win32::API::More->Import(
57+
crypt32 => "CertGetNameStringA", "NIIPPI", "I"
58+
);
59+
60+
sub getKeyStore {
61+
my (%params) = @_;
62+
63+
my $logger = $params{logger};
64+
65+
my @stores = $params{store} ? ($params{store}) : qw(
66+
ROOT
67+
CA
68+
TRUST
69+
MY
70+
);
71+
72+
my @certs;
73+
74+
foreach my $store (@stores) {
75+
76+
next unless $store;
77+
78+
my $hCertStore = CertOpenSystemStoreA(0, $store);
79+
unless ($hCertStore) {
80+
$logger->error(_log_prefix."Failed to open system $store keystore")
81+
if $logger;
82+
next;
83+
}
84+
85+
my $count;
86+
my $pPrev = 0;
87+
88+
while ($pPrev = CertEnumCertificatesInStore($hCertStore, $pPrev)) {
89+
$count++;
90+
my $certName = " " x 256;
91+
my $length = CertGetNameStringA($pPrev, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, 0, $certName, 256);
92+
if ($length) {
93+
$certName = trimWhitespace($certName);
94+
$logger->debug(_log_prefix."$store-$count: Importing $certName")
95+
if $logger;
96+
}
97+
my $buffer = Win32::API::ReadMemory($pPrev, 3*16);
98+
if (empty($buffer)) {
99+
$logger->debug(_log_prefix."Failed to copy CERT_CONTEXT ($count)")
100+
if $logger;
101+
next;
102+
}
103+
my ($dwCertEncodingType, $pbCertEncoded, $cbCertEncoded) = unpack("Q*", $buffer);
104+
next unless $dwCertEncodingType && $dwCertEncodingType == X509_ASN_ENCODING;
105+
unless ($pbCertEncoded && $cbCertEncoded) {
106+
$logger->debug(_log_prefix."Got wrong CERT_CONTEXT copy ($count)")
107+
if $logger;
108+
next;
109+
}
110+
my $certbuffer = Win32::API::ReadMemory($pbCertEncoded, $cbCertEncoded);
111+
if (empty($certbuffer)) {
112+
$logger->debug(_log_prefix."Failed to copy certificate content ($count)")
113+
if $logger;
114+
next;
115+
}
116+
my $bio = Net::SSLeay::BIO_new(Net::SSLeay::BIO_s_mem());
117+
my $rv = Net::SSLeay::BIO_write($bio, $certbuffer);
118+
unless ($rv == $cbCertEncoded) {
119+
$logger->debug(_log_prefix."Failed to import certificate content ($count)")
120+
if $logger;
121+
Net::SSLeay::BIO_free($bio) if $rv > 0;
122+
next;
123+
}
124+
my $cert = Net::SSLeay::d2i_X509_bio($bio);
125+
Net::SSLeay::BIO_free($bio);
126+
push @certs, $cert;
127+
}
128+
129+
unless (CertCloseStore($hCertStore, 0)) {
130+
$logger->debug(_log_prefix."Failed to close $store keystore")
131+
if $logger;
132+
}
133+
134+
$logger->debug(_log_prefix."No certificate found in $store keystore")
135+
if !$count && $logger && @stores == 1;
136+
}
137+
138+
return @certs;
139+
}
140+
141+
1;

t/lib/fake/windows/Win32/API.pm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
package Win32::API;
22

3+
package Win32::API::More;
4+
5+
sub Import {}
6+
7+
package Win32::API::Type;
8+
9+
sub typedef {}
10+
311
1;

0 commit comments

Comments
 (0)