forked from OWASP/O-Saft
-
Notifications
You must be signed in to change notification settings - Fork 0
/
o-saft.pl
executable file
·8351 lines (6982 loc) · 372 KB
/
o-saft.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/perl -w -I ./
#!#############################################################################
#!# Copyright (c) Achim Hoffmann, sic[!]sec GmbH
#!#----------------------------------------------------------------------------
#!# If this tool is valuable for you and we meet some day, you can spend me an
#!# O-Saft. I'll accept good wine or beer too :-). Meanwhile -- 'til we meet --
#!# your're encouraged to make a donation to any needy child you see. Thanks!
#!#----------------------------------------------------------------------------
#!# This software is provided "as is", without warranty of any kind, express or
#!# implied, including but not limited to the warranties of merchantability,
#!# fitness for a particular purpose. In no event shall the copyright holders
#!# or authors be liable for any claim, damages or other liability.
#!# This software is distributed in the hope that it will be useful.
#!#
#!# This software is licensed under GPLv2.
#!#
#!# GPL - The GNU General Public License, version 2
#!# as specified in: http://www.gnu.org/licenses/gpl-2.0
#!# or a copy of it https://github.com/OWASP/O-Saft/blob/master/LICENSE.md
#!# Permits anyone the right to use and modify the software without limitations
#!# as long as proper credits are given and the original and modified source
#!# code are included. Requires that the final product, software derivate from
#!# the original source or any software utilizing a GPL component, such as
#!# this, is also licensed under the same GPL license.
#!#############################################################################
#!# WARNING:
#!# This is no "academically" certified code, but written to be understood and
#!# modified by humans (you:) easily. Please see the documentation in section
#!# "Program Code" at the end of this file if you want to improve the program.
# ToDo please see =begin ToDo in POD section
use strict;
use lib ("./lib"); # uncomment as needed
my $SID = "@(#) yeast.pl 1.279 14/06/09 13:40:05";
my @DATA = <DATA>;
our $VERSION= "--is defined at end of this file, and I hate to write it twice--";
{ # (perl is clever enough to extract it from itself ;-)
$VERSION= join ("", @DATA);
$VERSION=~ s/.*?\n@\(#\)\s*([^\n]*).*/$1/ms;
};
our $me = $0; $me =~ s#.*[/\\]##;
our $mepath = $0; $mepath =~ s#/[^/\\]*$##;
$mepath = "./" if ($mepath eq $me);
our $mename = "yeast ";
$mename = "O-Saft " if ($me !~ /yeast/);
binmode(STDOUT, ":unix");
binmode(STDERR, ":unix");
use IO::Socket::SSL 1.37; # qw(debug2);
use IO::Socket::INET;
our $warning= 1; # print warnings; need this variable very early
sub _warn { local $\="\n"; print("**WARNING: ", join(" ", @_)) if ($main::warning > 0); }
# print warning if wanted
## README if any
## -------------------------------------
open(RC, '<', "o-saft-README") && do { print <RC>; close(RC); exit 0; };
## CGI
## -------------------------------------
my $cgi = 0;
if ($me =~/\.cgi$/) {
# CGI mode is pretty simple: see {yeast,o-saft}.cgi
# code removed here!
die "**ERROR: CGI mode requires strict settings" if ($cgi !~ /--cgi=?/);
$cgi = 1;
}
## quick&dirty checks
## -------------------------------------
if (!defined $Net::SSLeay::VERSION) { # Net::SSLeay auto-loaded by IO::Socket::SSL
die "**ERROR: Net::SSLeay not found, useless use of yet another SSL tool";
# ToDo: this is not really true, i.e. if we use openssl instead Net::SSLeay
}
if (1.49 > $Net::SSLeay::VERSION) {
# only check VERSION instead of requiring a specific version with perl's use
# this allows continueing to use this tool even if the version is too old
# but we shout out loud that the results are not reliable
_warn("ancient Net::SSLeay $Net::SSLeay::VERSION found");
_warn("$0 requires Net::SSLeay 1.49 or newer");
_warn("$0 may throw warnings and/or results may be missing");
}
if (! eval("require Net::SSLinfo;")) {
# Net::SSLinfo may not be installed, try to find in program's directory
push(@INC, $mepath);
require Net::SSLinfo;
}
sub _print_read($$) { printf("=== reading %s from %s ===\n", @_) if(grep(/(:?--no.?header)/i, @ARGV) <= 0); }
# print information what will be read
# $cfg{'out_header'} not yet available, see LIMITATIONS also
my $arg = "";
# array to collect data fordebugging, they are global!
our @dbxarg; # normal options and arguments
our @dbxcfg; # config options and arguments
our @dbxexe; # executable, library, environment
our @dbxfile; # read files
## read file with user source, if any
## -------------------------------------
my @usr = grep(/--(?:use?r)/, @ARGV); # must have --usr option
if (($#usr >= 0) and ($cgi == 0)) {
$arg = "./o-saft-usr.pm";
if (! -e $arg) {
$arg = join("/", $mepath, $arg);# try to find it in installation directory
}
}
if (-e $arg) {
push(@dbxfile, $arg);
_print_read("user file", $arg) if(grep(/(:?--no.?header)/i, @ARGV) <= 0);
require $arg;
} else {
sub usr_pre_file() {}; # dummy stub, see o-saft-usr.pm
sub usr_pre_args() {}; # "
sub usr_pre_exec() {}; # "
sub usr_pre_cipher(){}; # "
sub usr_pre_main() {}; # "
sub usr_pre_host() {}; # "
sub usr_pre_info() {}; # "
sub usr_pre_open() {}; # "
sub usr_pre_cmds() {}; # "
sub usr_pre_data() {}; # "
sub usr_pre_print() {}; # "
sub usr_pre_next() {}; # "
sub usr_pre_exit() {}; # "
}
my @argv = grep(/--trace.?arg/i, @ARGV);# preserve --tracearg option
usr_pre_file();
## read .rc-file if any
## -------------------------------------
my @rc_argv = "";
$arg = "./.$me";
if (grep(/(:?--no.?rc)$/i, @ARGV) <= 0) { # only if not inhibit
open(RC, '<', "./.$me") && do {
push(@dbxfile, $arg);
_print_read("options", $arg) if ($cgi == 0);
@rc_argv = grep(!/\s*#[^\r\n]*/, <RC>); # remove comment lines
@rc_argv = grep(s/[\r\n]//, @rc_argv); # remove newlines
close(RC);
push(@argv, @rc_argv);
#dbx# _dbx ".RC: " . join(" ", @rc_argv) . "\n";
};
}
push(@argv, @ARGV);
#dbx# _dbx "ARG: " . join(" ", @argv);
## read file with source for trace and verbose, if any
## -------------------------------------
my @dbx = grep(/--(?:trace|v$|yeast)/, @argv); # option can be in .rc-file, hence @argv
if (($#dbx >= 0) and ($cgi == 0)) {
$arg = "./o-saft-dbx.pm";
$arg = $dbx[0] if ($dbx[0] =~ m#/#);
$arg =~ s#[^=]+=##; # --trace=./myfile.pl
if (! -e $arg) {
_warn("'$arg' not found");
$arg = join("/", $mepath, $arg); # try to find it in installation directory
die "**ERROR: '$!' '$arg'; exit" unless (-e $arg);
# no need to continue if required file does not exist
# Note: if $mepath or $0 is a symbolic link, above checks fail
# we don't fix that! Workaround: install file in ./
}
push(@dbxfile, $arg);
_print_read("trace file", $arg) if(grep(/(:?--no.?header)/i, @argv) <= 0);
# allow --no-header in RC-FILE also
require $arg; # `our' variables are available there
}
## initialize defaults
## -------------------------------------
#!# set defaults
#!# -------------------------------------
#!# To make (programmer's) life simple, we try to avoid complex data structure,
#!# which are error-prone, by using a couple of global variables.
#!# As there are no plans to run this tool in threaded mode, this should be ok.
#!# Please see "Program Code" in the POD section too.
#!#
#!# Here's an overview of the used global variables:
#!# $me - the program name or script name with path stripped off
#!# $mepath - the path where program or script ($me) is located
#!# $mename - my name pretty printed
#!# @results - where we store the results as: [SSL, cipher, "yes|no"]
#!# %data - labels and correspondig value (from Net::SSLinfo)
#!# %checks - collected and checked certificate data
#!# collected and checked target (connection) data
#!# collected and checked connection data
#!# collected and checked length and count data
#!# HTTP vs HTTPS checks
#!# %shorttexts - same as %checks, but short texts
#!# %cmd - configuration for external commands
#!# %cfg - configuration for commands and options
#!# %text - configuration for message texts
#!# %scores - scoring values
#!# %ciphers_desc - description of %ciphers data structure
#!# %ciphers - our ciphers
#!# %cipher_names - (hash)map of cipher constant-names to names
#!#
#!# All %check_* contain a default 'score' value of 10, see --cfg_score
#!# option how to change that.
# Note: all keys in data and check_* must be unique 'cause of shorttexts!!
#
# Note according perlish programming style:
# references to $arr->{'val') are most often simplified as $arr->{val)
# same applies to 'txt', 'typ' and 'score'
my ($key,$sec,$ssl);# some temporary variables used in main
my $host = ""; # the host currently processed in main
my $port = ""; # the port currently used in main
my $legacy = ""; # the legacy mode used in main
my $verbose = 0; # verbose mode used in main
# above host, port, legacy and verbose are just shortcuts for corresponding
# values in $cfg{}, used for better human readability
my $info = 0; # set to 1 if +info or +sni_check was used
my $check = 0; # set to 1 if +check was used
my $quick = 0; # set to 1 if +quick was used
our @results= (); # list of checked ciphers: [SSL, ciper suite name, yes|no]
our %data = ( # values from Net::SSLinfo, will be processed in print_data()
#!#----------------+-----------------------------------------------------------+-----------------------------------
#!# +command value from Net::SSLinfo::*() label to be printed
#!#----------------+-----------------------------------------------------------+-----------------------------------
'cn_nosni' => {'val' => "", 'txt' => "Certificate CN without SNI"},
'pem' => {'val' => sub { Net::SSLinfo::pem( $_[0], $_[1])}, 'txt' => "Certificate PEM"},
'text' => {'val' => sub { Net::SSLinfo::text( $_[0], $_[1])}, 'txt' => "Certificate PEM decoded"},
'cn' => {'val' => sub { Net::SSLinfo::cn( $_[0], $_[1])}, 'txt' => "Certificate Common Name"},
'subject' => {'val' => sub { Net::SSLinfo::subject( $_[0], $_[1])}, 'txt' => "Certificate Subject"},
'issuer' => {'val' => sub { Net::SSLinfo::issuer( $_[0], $_[1])}, 'txt' => "Certificate Issuer"},
'altname' => {'val' => sub { Net::SSLinfo::altname( $_[0], $_[1])}, 'txt' => "Certificate Subject's Alternate Names"},
'default' => {'val' => sub { Net::SSLinfo::default( $_[0], $_[1])}, 'txt' => "Default Cipher"},
'ciphers_openssl'=>{'val' => sub { $_[0] }, 'txt' => "OpenSSL Ciphers"},
'ciphers' => {'val' => sub { join(" ", Net::SSLinfo::ciphers($_[0], $_[1]))}, 'txt' => "Client Ciphers"},
'dates' => {'val' => sub { join(" .. ", Net::SSLinfo::dates($_[0], $_[1]))}, 'txt' => "Certificate Validity (date)"},
'before' => {'val' => sub { Net::SSLinfo::before( $_[0], $_[1])}, 'txt' => "Certificate valid since"},
'after' => {'val' => sub { Net::SSLinfo::after( $_[0], $_[1])}, 'txt' => "Certificate valid until"},
'aux' => {'val' => sub { Net::SSLinfo::aux( $_[0], $_[1])}, 'txt' => "Certificate Trust Information"},
'email' => {'val' => sub { Net::SSLinfo::email( $_[0], $_[1])}, 'txt' => "Certificate Email Addresses"},
'pubkey' => {'val' => sub { Net::SSLinfo::pubkey( $_[0], $_[1])}, 'txt' => "Certificate Public Key"},
'pubkey_algorithm'=>{'val'=> sub { Net::SSLinfo::pubkey_algorithm($_[0],$_[1])},'txt' => "Certificate Public Key Algorithm"},
'pubkey_value' => {'val' => sub { __SSLinfo('pubkey_value', $_[0], $_[1])}, 'txt' => "Certificate Public Key Value"},
'modulus_len' => {'val' => sub { Net::SSLinfo::modulus_len( $_[0], $_[1])}, 'txt' => "Certificate Public Key Length"},
'modulus' => {'val' => sub { Net::SSLinfo::modulus( $_[0], $_[1])}, 'txt' => "Certificate Public Key Modulus"},
'modulus_exponent'=>{'val'=> sub { Net::SSLinfo::modulus_exponent($_[0],$_[1])},'txt' => "Certificate Public Key Exponent"},
'serial' => {'val' => sub { Net::SSLinfo::serial( $_[0], $_[1])}, 'txt' => "Certificate Serial Number"},
'certversion' => {'val' => sub { Net::SSLinfo::version( $_[0], $_[1])}, 'txt' => "Certificate Version"},
'sigdump' => {'val' => sub { Net::SSLinfo::sigdump( $_[0], $_[1])}, 'txt' => "Certificate Signature (hexdump)"},
'sigkey_len' => {'val' => sub { Net::SSLinfo::sigkey_len( $_[0], $_[1])}, 'txt' => "Certificate Signature Key Length"},
'signame' => {'val' => sub { Net::SSLinfo::signame( $_[0], $_[1])}, 'txt' => "Certificate Signature Algorithm"},
'sigkey_value' => {'val' => sub { __SSLinfo('sigkey_value', $_[0], $_[1])}, 'txt' => "Certificate Signature Key Value"},
'trustout' => {'val' => sub { Net::SSLinfo::trustout( $_[0], $_[1])}, 'txt' => "Certificate trusted"},
'extensions' => {'val' => sub { __SSLinfo('extensions', $_[0], $_[1])}, 'txt' => "Certificate extensions"},
'tlsextdebug' => {'val' => sub { __SSLinfo('tlsextdebug', $_[0], $_[1])}, 'txt' => "SSL extensions (debug)"},
'tlsextensions' => {'val' => sub { __SSLinfo('tlsextensions', $_[0], $_[1])}, 'txt' => "SSL extensions"},
'ext_authority' => {'val' => sub { __SSLinfo('ext_authority', $_[0], $_[1])}, 'txt' => "Certificate extensions Authority Information Access"},
'ext_authorityid'=>{'val' => sub { __SSLinfo('ext_authorityid', $_[0], $_[1])}, 'txt' => "Certificate extensions Authority key Identifier"},
'ext_constraints'=>{'val' => sub { __SSLinfo('ext_constraints', $_[0], $_[1])}, 'txt' => "Certificate extensions Basic Constraints"},
'ext_cps' => {'val' => sub { __SSLinfo('ext_cps', $_[0], $_[1])}, 'txt' => "Certificate extensions Certificate Policies"},
'ext_cps_policy'=> {'val' => sub { __SSLinfo('ext_cps_policy', $_[0], $_[1])}, 'txt' => "Certificate extensions Certificate Policies: Policy"},
'ext_subjectkeyid'=>{'val'=> sub { __SSLinfo('ext_subjectkeyid',$_[0], $_[1])}, 'txt' => "Certificate extensions Subject Key Identifier"},
'ext_cps_cps' => {'val' => sub { __SSLinfo('ext_cps_cps', $_[0], $_[1])}, 'txt' => "Certificate extensions Certificate Policies: CPS"},
'ext_crl' => {'val' => sub { __SSLinfo('ext_crl', $_[0], $_[1])}, 'txt' => "Certificate extensions CRL Distribution Points"},
'ext_crl_crl' => {'val' => sub { __SSLinfo('ext_crl_crL', $_[0], $_[1])}, 'txt' => "Certificate extensions CRL Distribution Points: Full Name"},
'ext_keyusage' => {'val' => sub { __SSLinfo('ext_keyusage', $_[0], $_[1])}, 'txt' => "Certificate extensions Key Usage"},
'ext_extkeyusage'=>{'val' => sub { __SSLinfo('ext_extkeyusage', $_[0], $_[1])}, 'txt' => "Certificate extensions Extended Key Usage"},
'ext_certtype' => {'val' => sub { __SSLinfo('ext_certtype', $_[0], $_[1])}, 'txt' => "Certificate extensions Netscape Cert Type"},
'ext_issuer' => {'val' => sub { __SSLinfo('ext_issuer', $_[0], $_[1])}, 'txt' => "Certificate extensions Issuer Alternative Name"},
'ocsp_uri' => {'val' => sub { Net::SSLinfo::ocsp_uri( $_[0], $_[1])}, 'txt' => "Certificate OCSP Responder URL"},
'ocspid' => {'val' => sub { Net::SSLinfo::ocspid( $_[0], $_[1])}, 'txt' => "Certificate OCSP subject, public key hash"},
'subject_hash' => {'val' => sub { Net::SSLinfo::subject_hash( $_[0], $_[1])}, 'txt' => "Certificate Subject Name hash"},
'issuer_hash' => {'val' => sub { Net::SSLinfo::issuer_hash( $_[0], $_[1])}, 'txt' => "Certificate Issuer Name hash"},
'selfsigned' => {'val' => sub { Net::SSLinfo::selfsigned( $_[0], $_[1])}, 'txt' => "Certificate Validity (signature)"},
'fingerprint_type'=>{'val'=> sub { Net::SSLinfo::fingerprint_type($_[0],$_[1])},'txt' => "Certificate Fingerprint Algorithm"},
'fingerprint_hash'=>{'val'=> sub { __SSLinfo('fingerprint_hash',$_[0], $_[1])}, 'txt' => "Certificate Fingerprint Hash Value"},
'fingerprint_sha1'=>{'val'=> sub { __SSLinfo('fingerprint_sha1',$_[0], $_[1])}, 'txt' => "Certificate Fingerprint SHA1"},
'fingerprint_md5' =>{'val'=> sub { __SSLinfo('fingerprint_md5', $_[0], $_[1])}, 'txt' => "Certificate Fingerprint MD5"},
'fingerprint' => {'val' => sub { __SSLinfo('fingerprint', $_[0], $_[1])}, 'txt' => "Certificate Fingerprint"},
'cert_type' => {'val' => sub { Net::SSLinfo::cert_type( $_[0], $_[1])}, 'txt' => "Certificate Type (bitmask)"},
'sslversion' => {'val' => sub { Net::SSLinfo::SSLversion( $_[0], $_[1])}, 'txt' => "Selected SSL Protocol"},
'resumption' => {'val' => sub { Net::SSLinfo::resumption( $_[0], $_[1])}, 'txt' => "Target supports resumption"},
'renegotiation' => {'val' => sub { Net::SSLinfo::renegotiation( $_[0], $_[1])}, 'txt' => "Target supports renegotiation"},
'compression' => {'val' => sub { Net::SSLinfo::compression( $_[0], $_[1])}, 'txt' => "Target supports compression"},
'expansion' => {'val' => sub { Net::SSLinfo::expansion( $_[0], $_[1])}, 'txt' => "Target supports expansion"},
'krb5' => {'val' => sub { Net::SSLinfo::krb5( $_[0], $_[1])}, 'txt' => "Target supports Krb5"},
'psk_hint' => {'val' => sub { Net::SSLinfo::psk_hint( $_[0], $_[1])}, 'txt' => "Target supports PSK identity hint"},
'psk_identity' => {'val' => sub { Net::SSLinfo::psk_identity( $_[0], $_[1])}, 'txt' => "Target supports PSK"},
'srp' => {'val' => sub { Net::SSLinfo::srp( $_[0], $_[1])}, 'txt' => "Target supports SRP"},
'heartbeat' => {'val' => sub { __SSLinfo('heartbeat', $_[0], $_[1])}, 'txt' => "Target supports heartbeat"},
'protocols' => {'val' => sub { Net::SSLinfo::protocols( $_[0], $_[1])}, 'txt' => "Target supported protocols"},
'master_key' => {'val' => sub { Net::SSLinfo::master_key( $_[0], $_[1])}, 'txt' => "Target's Master-Key"},
'session_id' => {'val' => sub { Net::SSLinfo::session_id( $_[0], $_[1])}, 'txt' => "Target's Session-ID"},
'session_ticket'=> {'val' => sub { Net::SSLinfo::session_ticket($_[0], $_[1])}, 'txt' => "Target's TLS Session Ticket"},
'chain' => {'val' => sub { Net::SSLinfo::chain( $_[0], $_[1])}, 'txt' => "Certificate Chain"},
'chain_verify' => {'val' => sub { Net::SSLinfo::chain_verify( $_[0], $_[1])}, 'txt' => "CA Chain Verification (trace)"},
'verify' => {'val' => sub { Net::SSLinfo::verify( $_[0], $_[1])}, 'txt' => "Validity Certificate Chain"},
'error_verify' => {'val' => sub { Net::SSLinfo::error_verify( $_[0], $_[1])}, 'txt' => "CA Chain Verification error"},
'error_depth' => {'val' => sub { Net::SSLinfo::error_depth( $_[0], $_[1])}, 'txt' => "CA Chain Verification error in level"},
'verify_altname'=> {'val' => sub { Net::SSLinfo::verify_altname($_[0], $_[1])}, 'txt' => "Validity Alternate Names"},
'verify_hostname'=>{'val' => sub { Net::SSLinfo::verify_hostname( $_[0],$_[1])},'txt' => "Validity Hostname"},
'https_status' => {'val' => sub { Net::SSLinfo::https_status( $_[0], $_[1])}, 'txt' => "HTTPS Status line"},
'https_server' => {'val' => sub { Net::SSLinfo::https_server( $_[0], $_[1])}, 'txt' => "HTTPS Server banner"},
'https_location'=> {'val' => sub { Net::SSLinfo::https_location($_[0], $_[1])}, 'txt' => "HTTPS Location header"},
'https_refresh' => {'val' => sub { Net::SSLinfo::https_refresh( $_[0], $_[1])}, 'txt' => "HTTPS Refresh header"},
'https_alerts' => {'val' => sub { Net::SSLinfo::https_alerts( $_[0], $_[1])}, 'txt' => "HTTPS Error alerts"},
'https_pins' => {'val' => sub { Net::SSLinfo::https_pins( $_[0], $_[1])}, 'txt' => "HTTPS Public Key Pins"},
'https_sts' => {'val' => sub { Net::SSLinfo::https_sts( $_[0], $_[1])}, 'txt' => "HTTPS STS header"},
'hsts_maxage' => {'val' => sub { Net::SSLinfo::hsts_maxage( $_[0], $_[1])}, 'txt' => "HTTPS STS MaxAge"},
'hsts_subdom' => {'val' => sub { Net::SSLinfo::hsts_subdom( $_[0], $_[1])}, 'txt' => "HTTPS STS include sub-domains"},
'http_status' => {'val' => sub { Net::SSLinfo::http_status( $_[0], $_[1])}, 'txt' => "HTTP Status line"},
'http_location' => {'val' => sub { Net::SSLinfo::http_location( $_[0], $_[1])}, 'txt' => "HTTP Location header"},
'http_refresh' => {'val' => sub { Net::SSLinfo::http_refresh( $_[0], $_[1])}, 'txt' => "HTTP Refresh header"},
'http_sts' => {'val' => sub { Net::SSLinfo::http_sts( $_[0], $_[1])}, 'txt' => "HTTP STS header"},
#------------------+---------------------------------------+-------------------------------------------------------
'options' => {'val' => sub { Net::SSLinfo::options( $_[0], $_[1])}, 'txt' => "<<internal>> used SSL options bitmask"},
# following are used for checkdates() only, they must not be a command!
# they are not printed with +info or +check; values are integer
'valid-years' => {'val' => 0, 'txt' => "certificate validity in years"},
'valid-months' => {'val' => 0, 'txt' => "certificate validity in months"},
'valid-days' => {'val' => 0, 'txt' => "certificate validity in days"}, # approx. value, accurate if < 30
); # %data
# need s_client for: compression|expansion|selfsigned|chain|verify|resumption|renegotiation|protocols|
# need s_client for: krb5|psk_hint|psk_identity|srp|master_key|session_id|session_ticket|
our %checks = (
# key => {val => "", txt => "label to be printed", score => 0, typ => "connection"},
#
# default for 'val' is "" (empty string), default for 'score' is 0
# 'typ' is any of certificate, connection, destination, https, sizes
# both will be set in sub _init_all(), please see below
# the default value means "check = ok/yes", otherwise: "check =failed/no"
); # %checks
my %check_cert = (
# collected and checked certificate data
#------------------+-----------------------------------------------------
# key label to be printed (description)
#------------------+-----------------------------------------------------
'verify' => {'txt' => "Certificate chain validated"},
'fp_not_md5' => {'txt' => "Certificate Fingerprint is not MD5"},
'dates' => {'txt' => "Certificate is valid"},
'expired' => {'txt' => "Certificate is not expired"},
'certfqdn' => {'txt' => "Certificate is valid according given hostname"},
'wildhost' => {'txt' => "Certificate's wildcard does not match hostname"},
'wildcard' => {'txt' => "Certificate does not contain wildcards"},
'rootcert' => {'txt' => "Certificate is not root CA"},
'selfsigned' => {'txt' => "Certificate is not self-signed"},
'dv' => {'txt' => "Certificate Domain Validation (DV)"},
'ev+' => {'txt' => "Certificate strict Extended Validation (EV)"},
'ev-' => {'txt' => "Certificate lazy Extended Validation (EV)"},
'ocsp' => {'txt' => "Certificate has OCSP Responder URL"},
'cps' => {'txt' => "Certificate has Certification Practice Statement"},
'crl' => {'txt' => "Certificate has CRL Distribution Points"},
'zlib' => {'txt' => "Certificate has (TLS extension) compression"},
'lzo' => {'txt' => "Certificate has (GnuTLS extension) compression"},
'open_pgp' => {'txt' => "Certificate has (TLS extension) authentication"},
'sernumber' => {'txt' => "Certificate Serial Number size RFC5280"},
'constraints' => {'txt' => "Certificate Basic Constraints is false"},
# following checks in subjectAltName, CRL, OCSP, CN, O, U
'nonprint' => {'txt' => "Certificate does not contain non-printable characters"},
'crnlnull' => {'txt' => "Certificate does not contain CR, NL, NULL characters"},
'ev-chars' => {'txt' => "Certificate has no invalid characters in extensions"},
# ToDo: SRP is a target feature but also named a `Certificate (TLS extension)'
# 'srp' => {'txt' => "Certificate has (TLS extension) authentication"},
#------------------+-----------------------------------------------------
# extensions:
# KeyUsage:
# 0 - digitalSignature
# 1 - nonRepudiation
# 2 - keyEncipherment
# 3 - dataEncipherment
# 4 - keyAgreement
# 5 - keyCertSign # indicates this is CA cert
# 6 - cRLSign
# 7 - encipherOnly
# 8 - decipherOnly
# verify, is-trusted: certificate must be trusted, not expired (after also)
# common name or altname matches given hostname
# 1 - no chain of trust
# 2 - not before
# 4 - not after
# 8 - hostname mismatch
# 16 - revoked
# 32 - bad common name
# 64 - self-signed
# possible problems with chains:
# - contains untrusted certificate
# - chain incomplete/not resolvable
# - chain too long (depth)
# - chain size too big
# - contains illegal characters
# ToDo: wee need an option to specify the the local certificate storage!
); # %check_cert
my %check_conn = (
# collected and checked connection data
#------------------+-----------------------------------------------------
'ip' => {'txt' => "IP for given hostname "},
'reversehost' => {'txt' => "Given hostname is same as reverse resolved hostname"},
'hostname' => {'txt' => "Connected hostname matches certificate's subject"},
'beast-default' => {'txt' => "Connection is safe against BEAST attack (default cipher)"},
'beast' => {'txt' => "Connection is safe against BEAST attack (any cipher)"},
'breach' => {'txt' => "Connection is safe against BREACH attack"},
'crime' => {'txt' => "Connection is safe against CRIME attack"},
'time' => {'txt' => "Connection is safe against TIME attack"},
'heartbleed' => {'txt' => "Connection is safe against heartbleed attack"},
'sni' => {'txt' => "Connection is not based on SNI"},
'default' => {'txt' => "Default cipher for "}, # used for @cfg{version} only
# counter for accepted ciphers, 0 if not supported
'SSLv2' => {'txt' => "Supported ciphers for SSLv2 (total)"},
'SSLv3' => {'txt' => "Supported ciphers for SSLv3 (total)"},
'TLSv1' => {'txt' => "Supported ciphers for TLSv1 (total)"},
'TLSv11' => {'txt' => "Supported ciphers for TLSv11 (total)"},
'TLSv12' => {'txt' => "Supported ciphers for TLSv12 (total)"},
'TLSv13' => {'txt' => "Supported ciphers for TLSv13 (total)"},
'DTLSv1' => {'txt' => "Supported ciphers for DTLSv1 (total)"},
# counter for this type of cipher
'SSLv2-LOW' => {'txt' => "Supported LOW security ciphers"},
'SSLv2-WEAK' => {'txt' => "Supported WEAK security ciphers"},
'SSLv2-HIGH' => {'txt' => "Supported HIGH security ciphers"},
'SSLv2-MEDIUM' => {'txt' => "Supported MEDIUM security ciphers"},
'SSLv2--?-' => {'txt' => "Supported unknown security ciphers"},
'SSLv3-LOW' => {'txt' => "Supported LOW security ciphers"},
'SSLv3-WEAK' => {'txt' => "Supported WEAK security ciphers"},
'SSLv3-HIGH' => {'txt' => "Supported HIGH security ciphers"},
'SSLv3-MEDIUM' => {'txt' => "Supported MEDIUM security ciphers"},
'SSLv3--?-' => {'txt' => "Supported unknown security ciphers"},
'TLSv1-LOW' => {'txt' => "Supported LOW security ciphers"},
'TLSv1-WEAK' => {'txt' => "Supported WEAK security ciphers"},
'TLSv1-HIGH' => {'txt' => "Supported HIGH security ciphers"},
'TLSv1-MEDIUM' => {'txt' => "Supported MEDIUM security ciphers"},
'TLSv1--?-' => {'txt' => "Supported unknown security ciphers"},
'TLSv11-LOW' => {'txt' => "Supported LOW security ciphers"},
'TLSv11-WEAK' => {'txt' => "Supported WEAK security ciphers"},
'TLSv11-HIGH' => {'txt' => "Supported HIGH security ciphers"},
'TLSv11-MEDIUM' => {'txt' => "Supported MEDIUM security ciphers"},
'TLSv11--?-' => {'txt' => "Supported unknown security ciphers"},
'TLSv12-LOW' => {'txt' => "Supported LOW security ciphers"},
'TLSv12-WEAK' => {'txt' => "Supported WEAK security ciphers"},
'TLSv12-HIGH' => {'txt' => "Supported HIGH security ciphers"},
'TLSv12-MEDIUM' => {'txt' => "Supported MEDIUM security ciphers"},
'TLSv12--?-' => {'txt' => "Supported unknown security ciphers"},
'TLSv13-LOW' => {'txt' => "Supported LOW security ciphers"},
'TLSv13-WEAK' => {'txt' => "Supported WEAK security ciphers"},
'TLSv13-HIGH' => {'txt' => "Supported HIGH security ciphers"},
'TLSv13-MEDIUM' => {'txt' => "Supported MEDIUM security ciphers"},
'TLSv13--?-' => {'txt' => "Supported unknown security ciphers"},
'DTLSv1-LOW' => {'txt' => "Supported LOW security ciphers"},
'DTLSv1-WEAK' => {'txt' => "Supported WEAK security ciphers"},
'DTLSv1-HIGH' => {'txt' => "Supported HIGH security ciphers"},
'DTLSv1-MEDIUM' => {'txt' => "Supported MEDIUM security ciphers"},
'DTLSv1--?-' => {'txt' => "Supported unknown security ciphers"},
#------------------+-----------------------------------------------------
); # %check_conn
my %check_dest = (
# collected and checked target (connection) data
#------------------+-----------------------------------------------------
'sgc' => {'txt' => "Target supports Server Gated Cryptography (SGC)"},
'hasSSLv2' => {'txt' => "Target supports only safe protocols (no SSL 2.0)"},
'edh' => {'txt' => "Target supports EDH ciphers"},
'adh' => {'txt' => "Target does not accepts ADH ciphers"},
'null' => {'txt' => "Target does not accepts NULL ciphers"},
'export' => {'txt' => "Target does not accepts EXPORT ciphers"},
'rc4' => {'txt' => "Target does not accepts RC4 ciphers"},
'closure' => {'txt' => "Target understands TLS closure alerts"},
'fallback' => {'txt' => "Target supports fallback from TLSv1.1"},
'order' => {'txt' => "Target honors client's cipher order"},
'ism' => {'txt' => "Target supports ISM compliant ciphers"},
'pci' => {'txt' => "Target supports PCI compliant ciphers"},
'fips' => {'txt' => "Target supports FIPS-140 compliant ciphers"},
'tr-02102' => {'txt' => "Target supports TR-02102-2 compliant ciphers"},
'bsi-tr-02102+' => {'txt' => "Target is strict BSI TR-02102-2 compliant"},
'bsi-tr-02102-' => {'txt' => "Target is lazy BSI TR-02102-2 compliant"},
'resumption' => {'txt' => "Target supports resumption"},
'renegotiation' => {'txt' => "Target supports renegotiation"},
'pfs' => {'txt' => "Target supports forward secrecy (PFS)"},
'krb5' => {'txt' => "Target supports Krb5"},
'psk_hint' => {'txt' => "Target supports PSK identity hint"},
'psk_identity' => {'txt' => "Target supports PSK"},
'srp' => {'txt' => "Target supports SRP"},
'session_ticket'=> {'txt' => "Target supports TLS Session Ticket"}, # sometimes missing ...
'heartbeat' => {'txt' => "Target does not support heartbeat extension"},
'scsv' => {'txt' => "Target does not support SCSV"},
# following for information, checks not useful; see "# check target specials" in checkdest also
# 'master_key' => {'txt' => "Target supports Master-Key"},
# 'session_id' => {'txt' => "Target supports Session-ID"},
#------------------+-----------------------------------------------------
); # %check_dest
my %check_size = (
# collected and checked length and count data
# counts and sizes are integer values, key mast have prefix (len|cnt)_
#------------------+-----------------------------------------------------
'len_pembase64' => {'txt' => "Certificate PEM (base64) size"}, # <(2048/8*6)
'len_pembinary' => {'txt' => "Certificate PEM (binary) size"}, # < 2048
'len_subject' => {'txt' => "Certificate Subject size"}, # < 256
'len_issuer' => {'txt' => "Certificate Issuer size"}, # < 256
'len_CPS' => {'txt' => "Certificate CPS size"}, # < 256
'len_CRL' => {'txt' => "Certificate CRL size"}, # < 256
'len_CRL_data' => {'txt' => "Certificate CRL data size"},
'len_OCSP' => {'txt' => "Certificate OCSP size"}, # < 256
'len_OIDs' => {'txt' => "Certificate OIDs size"},
'len_publickey' => {'txt' => "Certificate Public Key size"}, # > 1024
'len_sigdump' => {'txt' => "Certificate Signature Key size"} ,# > 1024
'len_altname' => {'txt' => "Certificate Subject Altname size"},
'len_chain' => {'txt' => "Certificate Chain size"}, # < 2048
'len_sernumber' => {'txt' => "Certificate Serial Number size"}, # <= 20 octets
'cnt_altname' => {'txt' => "Certificate Subject Altname count"}, # == 0
'cnt_wildcard' => {'txt' => "Certificate Wildcards count"}, # == 0
'cnt_chaindepth'=> {'txt' => "Certificate Chain Depth count"}, # == 1
'cnt_ciphers' => {'txt' => "Number of offered ciphers"}, # <> 0
'cnt_totals' => {'txt' => "Total number of checked ciphers"},
#------------------+-----------------------------------------------------
# ToDo: cnt_ciphers, len_chain, cnt_chaindepth
); # %check_size
my %check_http = (
# HTTP vs HTTPS checks
# score are absolute values here, they are set to 100 if attribute is found
# key must have prefix (hsts|sts); see $cfg{'regex'}->{'cmd-http'}
#------------------+-----------------------------------------------------
'sts_maxage0d' => {'txt' => "STS max-age not set"}, # very weak
'sts_maxage1d' => {'txt' => "STS max-age less than one day"}, # weak
'sts_maxage1m' => {'txt' => "STS max-age less than one month"}, # low
'sts_maxage1y' => {'txt' => "STS max-age less than one year"}, # medium
'sts_maxagexy' => {'txt' => "STS max-age more than one year"}, # high
'hsts_sts' => {'txt' => "Target sends STS header"},
'sts_maxage' => {'txt' => "Target sends STS header with proper max-age"},
'sts_subdom' => {'txt' => "Target sends STS header with includeSubdomain"},
'hsts_is301' => {'txt' => "Target redirects with status code 301"}, # RFC6797 requirement
'hsts_is30x' => {'txt' => "Target redirects not with 30x status code"}, # other than 301, 304
'hsts_fqdn' => {'txt' => "Target redirect matches given host"},
'http_https' => {'txt' => "Target redirects HTTP to HTTPS"},
'hsts_location' => {'txt' => "Target sends STS and no Location header"},
'hsts_refresh' => {'txt' => "Target sends STS and no Refresh header"},
'hsts_redirect' => {'txt' => "Target redirects HTTP without STS header"},
'hsts_ip' => {'txt' => "Target does not send STS header for IP"},
'pkp_pins' => {'txt' => "Target sends Public Key Pins header"},
#------------------+-----------------------------------------------------
); # %check_http
# now construct %checks from %check_* and set 'typ'
foreach $key (keys %check_conn) { $checks{$key}->{txt} = $check_conn{$key}->{txt}; $checks{$key}->{typ} = 'connection'; }
foreach $key (keys %check_cert) { $checks{$key}->{txt} = $check_cert{$key}->{txt}; $checks{$key}->{typ} = 'certificate'; }
foreach $key (keys %check_dest) { $checks{$key}->{txt} = $check_dest{$key}->{txt}; $checks{$key}->{typ} = 'destination'; }
foreach $key (keys %check_size) { $checks{$key}->{txt} = $check_size{$key}->{txt}; $checks{$key}->{typ} = 'sizes'; }
foreach $key (keys %check_http) { $checks{$key}->{txt} = $check_http{$key}->{txt}; $checks{$key}->{typ} = 'https'; }
our %data_oid = ( # ToDo: nothing YET IMPLEMENTED except for EV
# ToDo: generate this table using Net::SSLeay functions like:
# Net::SSLeay::OBJ_nid2ln(), Net::SSLeay::OBJ_ln2nid()
# Net::SSLeay::OBJ_nid2sn(), Net::SSLeay::OBJ_sn2nid(),
# Net::SSLeay::OBJ_nid2obj(), Net::SSLeay::OBJ_obj2nid(),
# Net::SSLeay::OBJ_txt2obj(), Net::SSLeay::OBJ_txt2nid(),
# Net::SSLeay::OBJ_obj2txt(),
# all constants and values are defined in openssl/crypto/objects/obj_dat.h
# print "nid ". Net::SSLeay::OBJ_txt2nid("CN"); # --> 13
# print "Nam ". Net::SSLeay::OBJ_obj2txt( Net::SSLeay::OBJ_txt2obj("1.3.6.1.5.5.7.3.3"), 0); # --> Code Signing
# print "nam ". Net::SSLeay::OBJ_obj2txt( Net::SSLeay::OBJ_txt2obj("CN"), 0); # --> commonName
# print "oid ". Net::SSLeay::OBJ_obj2txt( Net::SSLeay::OBJ_txt2obj("CN"), 1); # --> 2.5.4.3
# print "OID ". Net::SSLeay::OBJ_obj2txt( Net::SSLeay::OBJ_nid2obj( 13 ), 1); # --> 2.5.4.3
# we should use NIDs to generate the hash, as all other strings are
# case sensitive. get NIDs with:
# grep NID_ openssl/crypto/objects/objects.h | awk '{print $3}' | sort -n
# so we can loop from 0..180 (or 300 if checks are possible)
# see also: http://www.zytrax.com/books/ldap/apa/oid.html
#
# wir koennen dann einen Parser fuer OIDs bauen:
# loop ueber OID und dabei immer .N vom Ende wegnehmen und rest mit OBJ_obj2txt() ausgeben
# # 1.3.6.1.4 --> "" . identified-organization . dot . iana . Private
# # 2.5.29.32 --> "" . directory services (X.500) . id-ce . X509v3 Certificate Policies
# '1.3.6.1' => {iso(1) org(3) dod(6) iana(1)}
'1.3.6.1' => {'txt' => "Internet OID"},
'1.3.6.1.5.5.7.1.1' => {'txt' => "Authority Information Access"}, # authorityInfoAccess
'1.3.6.1.5.5.7.1.12' => {'txt' => "<<undef>>"},
'1.3.6.1.5.5.7.1.14' => {'txt' => "Proxy Certification Information"},
'1.3.6.1.5.5.7.3.1' => {'txt' => "Server Authentication"},
'1.3.6.1.5.5.7.3.2' => {'txt' => "Client Authentication"},
'1.3.6.1.5.5.7.3.3' => {'txt' => "Code Signing"},
'1.3.6.1.5.5.7.3.4' => {'txt' => "Email Protection"},
'1.3.6.1.5.5.7.3.5' => {'txt' => "IPSec end system"},
'1.3.6.1.5.5.7.3.6' => {'txt' => "IPSec tunnel"},
'1.3.6.1.5.5.7.3.7' => {'txt' => "IPSec user"},
'1.3.6.1.5.5.7.3.8' => {'txt' => "Timestamping"},
'1.3.6.1.4.1.11129.2.5.1' => {'txt' => "<<undef>>"}, # Certificate Policy?
'1.3.6.1.4.1.14370.1.6' => {'txt' => "<<undef>>"}, # Certificate Policy?
'1.3.6.1.4.1.311.10.3.3' => {'txt' => "Microsoft Server Gated Crypto"},
'1.3.6.1.4.1.311.10.11' => {'txt' => "Microsoft Server: EV additional Attributes"},
'1.3.6.1.4.1.311.10.11.11' => {'txt' => "Microsoft Server: EV ??friendly name??"},
'1.3.6.1.4.1.311.10.11.83' => {'txt' => "Microsoft Server: EV ??root program??"},
'1.3.6.1.4.1.4146.1.10' => {'txt' => "<<undef>>"}, # Certificate Policy?
'2.16.840.1.113730.4.1' => {'txt' => "Netscape SGC"},
'1.2.840.113549.1.1.1' => {'txt' => "SubjectPublicKeyInfo"}, # ???
# EV: OIDs used in EV Certificates
'2.5.4.10' => {'txt' => "EV Certificate: subject:organizationName"},
'2.5.4.11' => {'txt' => "EV Certificate: subject:organizationalUnitName"},
'2.5.4.15' => {'txt' => "EV Certificate: subject:businessCategory"},
'2.5.4.3' => {'txt' => "EV Certificate: subject:commonName"}, # or SubjectAlternativeName:dNSName
# EV: Jurisdiction of Incorporation or Registration
'1.3.6.1.4.1.311.60.2.1.1' => {'txt' => "EV Certificate: subject:jurisdictionOfIncorporationLocalityName"},
'1.3.6.1.4.1.311.60.2.1.2' => {'txt' => "EV Certificate: subject:jurisdictionOfIncorporationStateOrProvinceName"},
'1.3.6.1.4.1.311.60.2.1.3' => {'txt' => "EV Certificate: subject:jurisdictionOfIncorporationCountryName"},
'2.5.4.5' => {'txt' => "EV Certificate: subject:serialNumber"},
# EV: Physical Address of Place of Business
'2.5.4.6' => {'txt' => "EV Certificate: subject:countryName"},
'2.5.4.7' => {'txt' => "EV Certificate: subject:localityName"},
'2.5.4.8' => {'txt' => "EV Certificate: subject:stateOrProvinceName"},
'2.5.4.9' => {'txt' => "EV Certificate: subject:streetAddress"},
'2.5.4.17' => {'txt' => "EV Certificate: subject:postalCode"},
# EV: Compliance with European Union Qualified Certificates Standard In addition, RFC 3739
'1.3.6.1.4.1.311.60.2.1' => {'txt' => "EV Certificate: qcStatements:qcStatement:statementId"},
# EV: others
'1.3.6.1.4.1.311.60.1.1' => {'txt' => "EV Certificate: ??fake root??"},
'2.5.29.32.0' => {'txt' => "EV Certificate: subject:anyPolicy"},
'2.5.29.35' => {'txt' => "EV Certificate: subject:authorityKeyIdentifier"}, # Authority key id
'2.5.29.37' => {'txt' => "EV Certificate: subject:extendedKeyUsage"}, # Extended key usage
'0.9.2342.19200300.100.1.25'=> {'txt' => "EV Certificate: subject:domainComponent"},
# others
'2.5.4.4' => {'txt' => "subject:surname"},
'2.5.4.12' => {'txt' => "subject:title"},
'2.5.4.41' => {'txt' => "subject:name"},
'2.5.4.42' => {'txt' => "subject:givenName"},
'2.5.4.43' => {'txt' => "subject:intials"},
'2.5.4.44' => {'txt' => "subject:generationQualifier"},
'2.5.4.46' => {'txt' => "subject:dnQualifier"},
'2.5.29.14' => {'txt' => "subject:subjectKeyIdentifier"}, # Subject key id
'2.5.29.15' => {'txt' => "subject:keyUsage"}, # Key usage
'2.5.29.17' => {'txt' => "subject:subjectAlternateName"}, # Subject alternative name
'2.5.29.19' => {'txt' => "subject:basicConstraints"}, # Basic constraints
'2.5.29.31' => {'txt' => "subject:crlDistributionPoints"},# CRL distribution points
'2.5.29.32' => {'txt' => "subject:certificatePolicies"}, # Certificate Policies
'2.16.840.1.113733.1.7.23.6'=> {'txt' => "<<undef>>"}, # Certificate Policy?
'2.16.840.1.113733.1.7.48.1'=> {'txt' => "<<undef>>"}, # Certificate Policy?
'2.16.840.1.113733.1.7.54' => {'txt' => "<<undef>>"}, # Certificate Policy?
'0.9.2342.19200300.100.1.3' => {'txt' => "subject:mail"},
); # %data_oid
$data_oid{$_}->{val} = "<<check error>>" foreach (keys %data_oid);
our %shorttexts = (
#------------------+------------------------------------------------------
# %check +check short label text
#------------------+------------------------------------------------------
# Note: key must be same string as used in %ciphers[ssl] {
'SSLv2' => "Ciphers (SSLv2)",
'SSLv3' => "Ciphers (SSLv3)",
'TLSv1' => "Ciphers (TLSv1)",
'TLSv11' => "Ciphers (TLSv11)",
'TLSv12' => "Ciphers (TLSv12)",
'TLSv13' => "Ciphers (TLSv13)",
'DTLSv1' => "Ciphers (DTLSv1)",
#}
'TLSv1-HIGH' => "Ciphers HIGH", # FIXME: is this needed (5/2014)
'ip' => "IP for hostname",
'DNS' => "DNS for hostname",
'reversehost' => "Reverse hostname",
'hostname' => "Hostname matches Subject",
'expired' => "Not expired",
'certfqdn' => "Valid for hostname",
'wildhost' => "Wilcard for hostname",
'wildcard' => "No wildcards",
'sni' => "Not SNI based",
'sernumber' => "Serial Number size (RFC5280)",
'rootcert' => "Not root CA",
'ocsp' => "OCSP supported",
'hasSSLv2' => "No SSL 2.0",
'adh' => "No ADH ciphers",
'edh' => "EDH ciphers",
'null' => "No NULL ciphers",
'export' => "No EXPORT ciphers",
'rc4' => "No RC4 ciphers",
'sgc' => "SGC supported",
'cps' => "CPS supported",
'crl' => "CRL supported",
'dv' => "DV supported",
'ev+' => "Strict EV supported",
'ev-' => "Lazy EV supported",
'ev-chars' => "NO invalid characters in extensions",
'beast-default' => "Default cipher safe to BEAST",
'beast' => "Supported cipher safe to BEAST",
'breach' => "Safe to BREACH",
'crime' => "Safe to CRIME",
'time' => "Safe to TIME",
'heartbleed' => "Safe to heartbleed",
'scsv' => "SCSV not supported",
'constraints' => "Basic Constraints is false",
'closure' => "TLS closure alerts",
'fallback' => "Fallback from TLSv1.1",
'zlib' => "ZLIB extension",
'lzo' => "GnuTLS extension",
'open_pgp' => "OpenPGP extension",
'order' => "Client's cipher order",
'ism' => "ISM compliant",
'pci' => "PCI compliant",
'pfs' => "PFS supported",
'fips' => "FIPS-140 compliant",
'tr-02102' => "TR-02102-2 compliant",
'bsi-tr-02102+' => "Strict BSI TR-02102-2 compliant",
'bsi-tr-02102-' => "Lazy BSI TR-02102-2 compliant",
'resumption' => "Resumption",
'renegotiation' => "Renegotiation",
'hsts_sts' => "STS header",
'sts_maxage' => "STS long max-age",
'sts_subdom' => "STS includeSubdomain",
'hsts_ip' => "STS header not for IP",
'hsts_location' => "STS and Location header",
'hsts_refresh' => "STS and no Refresh header",
'hsts_redirect' => "Redirects without STS",
'http_https' => "Redirects HTTP",
'hsts_fqdn' => "Redirects to same host",
'hsts_is301' => "Redirects with 301",
'hsts_is30x' => "Redirects not with 30x",
'pkp_pins' => "Public Key Pins",
'selfsigned' => "Validity (signature)",
'chain' => "Certificate chain",
'chain_verify' => "CA Chain trace",
'verify' => "Chain verified",
'error_verify' => "CA Chain error",
'error_depth' => "CA Chain error in level",
'nonprint' => "No non-printables",
'crnlnull' => "No CR, NL, NULL",
'compression' => "Compression",
'expansion' => "Expansion",
'krb5' => "Krb5 Principal",
'psk_hint' => "PSK identity hint",
'psk_identity' => "PSK identity",
'srp' => "SRP username",
'protocols' => "Protocols",
'master_key' => "Master-Key",
'session_id' => "Session-ID",
'session_ticket'=> "TLS Session Ticket",
'len_pembase64' => "Size PEM (base64)",
'len_pembinary' => "Size PEM (binary)",
'len_subject' => "Size subject",
'len_issuer' => "Size issuer",
'len_CPS' => "Size CPS",
'len_CRL' => "Size CRL",
'len_CRL_data' => "Size CRL data",
'len_OCSP' => "Size OCSP",
'len_OIDs' => "Size OIDs",
'len_altname' => "Size altname",
'len_publickey' => "Size pubkey",
'len_sigdump' => "Size signature key",
'len_chain' => "Size certificate chain",
'cnt_altname' => "Count altname",
'cnt_wildcard' => "Count wildcards",
'cnt_chaindepth'=> "Count chain depth",
'cnt_ciphers' => "Count ciphers",
'cnt_totals' => "Checked ciphers",
#------------------+------------------------------------------------------
# %data +command short label text
#------------------+------------------------------------------------------
'pem' => "PEM",
'text' => "PEM decoded",
'cn' => "Common Name (CN)",
'subject' => "Subject",
'issuer' => "Issuer",
'altname' => "Subject AltNames",
'ciphers' => "Client Ciphers",
'default' => "Default Cipher",
'ciphers_openssl' => "OpenSSL Ciphers",
'dates' => "Validity (date)",
'before' => "Valid since",
'after' => "Valid until",
'tlsextdebug' => "SSL Extensions (debug)",
'tlsextensions' => "SSL Extensions",
'extensions' => "Extensions",
'heartbeat' => "Heartbeat", # not realy a `key', but a extension
'aux' => "Trust",
'email' => "Email",
'pubkey' => "Public Key",
'pubkey_algorithm' => "Public Key Algorithm",
'pubkey_value' => "Public Key Value",
'modulus_len' => "Public Key length",
'modulus' => "Public Key modulus",
'modulus_exponent' => "Public Key exponent",
'serial' => "Serial Number",
'certversion' => "Certificate Version",
'sslversion' => "SSL protocol",
'signame' => "Signature Algorithm",
'sigdump' => "Signature (hexdump)",
'sigkey_len' => "Signature key length",
'sigkey_value' => "Signature key value",
'trustout' => "Trusted",
'ocsp_uri' => "OCSP URL",
'ocspid' => "OCSP hash",
'subject_hash' => "Subject hash",
'issuer_hash' => "Issuer hash",
'fp_not_md5' => "Fingerprint not MD5",
'verify_hostname' => "Hostname valid",
'verify_altname' => "AltNames valid",
'fingerprint_hash' => "Fingerprint Hash",
'fingerprint_type' => "Fingerprint Algorithm",
'fingerprint_sha1' => "Fingerprint SHA1",
'fingerprint_md5' => "Fingerprint MD5",
'fingerprint' => "Fingerprint:",
'https_status' => "HTTPS Status line",
'https_server' => "HTTPS Server banner",
'https_alerts' => "HTTPS Error alerts",
'https_refresh' => "HTTPS Refresh header",
'https_pins' => "HTTPS Public Key Pins",
'https_sts' => "HTTPS STS header",
'hsts_maxage' => "HTTPS STS MaxAge",
'hsts_subdom' => "HTTPS STS sub-domains",
'hsts_is301' => "HTTP Status code is 301",
'hsts_is30x' => "HTTP Status code not 30x",
'http_status' => "HTTP Status line",
'http_location' => "HTTP Location header",
'http_refresh' => "HTTP Refresh header",
'http_sts' => "HTTP STS header",
#------------------+------------------------------------------------------
# more texts dynamically, see "adding more shorttexts" below
); # %shorttexts
my %scores = (
# keys starting with 'check_' are for total values
# all other keys are for individual score values
#------------------+-------------+----------------------------------------
'check_conn' => {'val' => 100, 'txt' => "SSL connection checks"},
'check_ciph' => {'val' => 100, 'txt' => "Ciphers checks"},
'check_cert' => {'val' => 100, 'txt' => "Certificate checks"},
'check_dest' => {'val' => 100, 'txt' => "Target checks"},
'check_http' => {'val' => 100, 'txt' => "HTTP(S) checks"},
'check_size' => {'val' => 100, 'txt' => "Certificate sizes checks"},
'checks' => {'val' => 100, 'txt' => "Total scoring"},
#------------------+-------------+----------------------------------------
# sorting according key name
); # %scores
my %score_ssllabs = (
# SSL Server Rating Guide:
#------------------+------------+---------------+-------------------------
'check_prot' => {'val' => 0, 'score' => 0.3, 'txt' => "Protocol support"}, # 30%
'check_keyx' => {'val' => 0, 'score' => 0.3, 'txt' => "Key exchange support"}, # 30%
'check_ciph' => {'val' => 0, 'score' => 0.4, 'txt' => "Cipher strength support"}, # 40%
# 'score' is a factor here; 'val' will be the score 0..100
# Letter grade translation
# Grade Numerical Score
#------------------------------------------+------+---------------
'A' => {'val' => 0, 'score' => 80, 'txt' => "A"}, # score >= 80
'B' => {'val' => 0, 'score' => 65, 'txt' => "B"}, # score >= 65
'C' => {'val' => 0, 'score' => 50, 'txt' => "C"}, # score >= 50
'D' => {'val' => 0, 'score' => 35, 'txt' => "D"}, # score >= 35
'E' => {'val' => 0, 'score' => 20, 'txt' => "E"}, # score >= 20
'F' => {'val' => 0, 'score' => 20, 'txt' => "F"}, # score >= 20
# 'val' is not used above!
# Protocol support rating guide
# Protocol Score Protocol
#------------------------------------------+-----+------------------
'SSLv2' => {'val' => 0, 'score' => 20, 'txt' => "SSL 2.0"}, # 20%
'SSLv2' => {'val' => 0, 'score' => 80, 'txt' => "SSL 3.0"}, # 80%
'TLSv1' => {'val' => 0, 'score' => 90, 'txt' => "TLS 1.0"}, # 90%
'TLSv11' => {'val' => 0, 'score' => 95, 'txt' => "TLS 1.1"}, # 95%
'TLSv12' => {'val' => 0, 'score' => 100, 'txt' => "TLS 1.2"}, # 100%
'TLSv13' => {'val' => 0, 'score' => 100, 'txt' => "TLS 1.3"}, # 100%
'DTLSv1' => {'val' => 0, 'score' => 100, 'txt' => "DTLS 1.0"},# 100%
# 'txt' is not used here!
#
# ( best protocol + worst protocol ) / 2
# Key exchange rating guide
# Score Key exchange aspect # Score
#------------------------------------------+-----+----------------------------------------------------------+------
'key_debian' => {'val' => 0, 'score' => 0, 'txt' => "Weak key (Debian OpenSSL flaw)"}, # 0%
'key_anonx' => {'val' => 0, 'score' => 0, 'txt' => "Anonymous key exchange (no authentication)"}, # 0%
'key_512' => {'val' => 0, 'score' => 20, 'txt' => "Key length < 512 bits"}, # 20%
'key_export' => {'val' => 0, 'score' => 40, 'txt' => "Exportable key exchange (limited to 512 bits)"}, # 40%
'key_1024' => {'val' => 0, 'score' => 40, 'txt' => "Key length < 1024 bits (e.g., 512)"}, # 40%
'key_2048' => {'val' => 0, 'score' => 80, 'txt' => "Key length < 2048 bits (e.g., 1024)"}, # 80%
'key_4096' => {'val' => 0, 'score' => 90, 'txt' => "Key length < 4096 bits (e.g., 2048)"}, # 90%
'key_good' => {'val' => 0, 'score' => 100, 'txt' => "Key length >= 4096 bits (e.g., 4096)"}, # 100%
#
#
# Cipher strength rating guide
# Score Cipher strength # Score
#------------------------------------------+-----+----------------------------------------+------
'ciph_0' => {'val' => 0, 'score' => 0, 'txt' => "0 bits (no encryption)"}, # 0%
'ciph_128' => {'val' => 0, 'score' => 0, 'txt' => "< 128 bits (e.g., 40, 56)"}, # 20%
'ciph_256' => {'val' => 0, 'score' => 0, 'txt' => "< 256 bits (e.g., 128, 168)"}, # 80%
'ciph_512' => {'val' => 0, 'score' => 0, 'txt' => ">= 256 bits (e.g., 256)"}, # 100%
#
# ( strongest cipher + weakest cipher ) / 2
#
); # %score_ssllabs
my %score_howsmyssl = (
# https://www.howsmyssl.com/
# https://www.howsmyssl.com/s/about.html
'good' => {'txt' => "Good"},
'probably' => {'txt' => "Probably Okay"},
'improvable' => {'txt' => "Improvable"},
# if they do not support ephemeral key cipher suites,
# do not support session tickets, or are using TLS 1.1.
'bad' => {'txt' => "Bad"},
# uses TLS 1.0 (instead of 1.1 or 1.2), or, worse, SSLv3 or earlier.
# supports known insecure cipher suites
# supports TLS compression (that is compression of the encryption
# information used to secure your connection) which exposes it
# to the CRIME attack.
# is susceptible to the BEAST attack
); # %score_howsmyssl
my %info_gnutls = ( # NOT YET USED
# extracted from http://www.gnutls.org/manual/gnutls.html
# security parameter ECC key
# bits size size security description
# ----------+-----------+--------+-----------+------------------
'I' => "<72 <1008 <160 INSECURE Considered to be insecure",
'W' => "72 1008 160 WEAK Short term protection against small organizations",
'L' => "80 1248 160 LOW Very short term protection against agencies",
'l' => "96 1776 192 LEGACY Legacy standard level",
'M' => "112 2432 224 NORMAL Medium-term protection",
'H' => "128 3248 256 HIGH Long term protection",
'S' => "256 15424 512 ULTRA Foreseeable future",
); # %info_gnutls
our %cmd = (
'timeout' => "timeout", # to terminate shell processes (timeout 1)
'openssl' => "openssl", # OpenSSL
'libs' => [], # where to find libssl.so and libcrypto.so
'path' => [], # where to find openssl executable
'extopenssl' => 1, # 1: use external openssl; default yes, except on Win32
'extsclient' => 1, # 1: use openssl s_client; default yes, except on Win32
'extciphers' => 0, # 1: use openssl s_client -cipher for connection check
'envlibvar' => "LD_LIBRARY_PATH", # name of environment variable
'call' => [], # list of special (internal) function calls
# see --call=METHOD option in description below
);
our %cfg = (
# config. key default description
#------------------+---------+----------------------------------------------
'try' => 0, # 1: do not execute openssl, just show
'exec' => 0, # 1: if +exec command used
'trace' => 0, # 1: trace yeast, 2=trace Net::SSLeay and Net::SSLinfo also
'traceARG' => 0, # 1: trace yeast's argument processing
'traceCMD' => 0, # 1: trace command processing
'traceKEY' => 0, # 1: (trace) print yeast's internal variable names
'verbose' => 0, # used for --v
'warning' => 1, # 1: print warnings; 0: don't print warnings
'proxyhost' => "", # FQDN or IP of proxy to be used
'proxyport' => 0, # port for proxy
'proxyauth' => "", # authentication string used for proxy
'proxyuser' => "", # username for proxy authentication (Basic or Digest Auth)
'proxypass' => "", # password for proxy authentication (Basic or Digest Auth)
'enabled' => 0, # 1: only print enabled ciphers
'disabled' => 0, # 1: only print disabled ciphers
'nolocal' => 0,
'usedns' => 1, # 1: make DNS reverse lookup
'usehttp' => 1, # 1: make HTTP request
'uselwp' => 0, # 1: use perls LWP module for HTTP checks # ToDo: NOT YET IMPLEMENTED
'forcesni' => 0, # 1: do not check if SNI seems to be supported by Net::SSLeay
'usesni' => 1, # 0: do not make connection in SNI mode;
'nomd5cipher' => 0, # 1: do not use *-MD5 ciphers except for SSLv2 with +cipher
'use_reconnect' => 1, # 0: do not use -reconnect option for openssl
'use_nextprot' => 1, # 0: do not use -nextprotoneg option for openssl
'use_extdebug' => 1, # 0: do not use -tlsextdebug option for openssl
'sclient_opt' => "", # argument or option passed to openssl s_client command
'no_cert' => 0, # 0: get data from certificate; 1, 2, do not get data
'no_cert_txt' => "", # change default text if no data from cert retrieved
'ca_depth' => undef, # depth of peer certificate verification verification
'ca_crl' => undef, # URL where to find CRL file
'ca_file' => undef, # PEM format file with CAs
'ca_path' => undef, # path to directory with PEM files for CAs
# see Net::SSLinfo why undef as default
'ca_paths' => [qw(/etc/ssl/certs /usr/lib/certs /System/Library/OpenSSL)],
'ca_files' => [qw(ca-certificates.crt certificates.crt certs.pem)],
'ignorecase' => 1, # 1: compare some strings case insensitive
'shorttxt' => 0, # 1: use short label texts
'version' => [], # contains the versions to be checked
'versions' => [qw(SSLv2 SSLv3 TLSv1 TLSv11 TLSv12 TLSv13 DTLSv1)],
# NOTE: must be same string as used in %ciphers[ssl]
# NOTE: must be same string as used in Net::SSLinfo %_SSLmap
# ToDo: DTLSv0.9
'ssl_lazy' => 0, # 1: lazy check for available SSL protocol functionality
'SSLv2' => 1, # 1: check this SSL version
'SSLv3' => 1, # 1: "
'TLSv1' => 1, # 1: "
'TLSv11' => 1, # 1: "
'TLSv12' => 1, # 1: "
'TLSv13' => 1, # 1: "
'DTLSv9' => 0, # 1: "
'DTLSv1' => 1, # 1: "
'nullssl2' => 0, # 1: complain if SSLv2 enabled but no ciphers accepted
'cipher' => [], # ciphers we got with --cipher=