]> www.wagner.pp.ru Git - openssl-gost/engine.git/blob - tcl_tests/ossltest.tcl
792c0718e35c409840b73b8d71bdcc8e06822423
[openssl-gost/engine.git] / tcl_tests / ossltest.tcl
1 #
2 # Расширение пакета test для OpenSSL
3 #
4 package require http
5 # Путь поиска пакета test
6 if {[info exists env(TOOLDIR)]} {
7         lappend auto_path $env(TOOLDIR)
8 } {
9         lappend auto_path "[file dirname [info script]]/../../maketool"
10 }
11
12
13 # outputs specified environment variables into log
14
15 proc log_vars {args} {
16         foreach var $args {
17                 if [info exists ::env($var)] {
18                         log $var=$::env($var)
19                 } else {
20                         log "$var is not set"
21                 }       
22         }
23 }       
24 # Проверка наличия необходимых переменных окружения
25 foreach var {OPENSSL_APP} {
26 if {![info exists env($var)]} {
27         puts stderr "Environment variable $var not defined"
28         exit 100
29 } else {
30         set $var [file normalize $env($var)]
31 }
32 }
33
34 if {[info exists env(OPENSSL_CONF)]} {
35         set OPENSSL_CONF $env(OPENSSL_CONF)
36 } else {
37         if {[regexp {OPENSSLDIR: "([^\"]+)"} [exec $OPENSSL_APP version -d] => openssl_dir]} {
38                 set OPENSSL_CONF $openssl_dir/openssl.cnf
39         } else {        
40                 puts stderr "Cannot find out default openssl config"
41                 exit 100
42         }
43 }       
44
45 if {![file exists $OPENSSL_CONF]} {
46         puts "Configuration file $OPENSSL_CONF doesn't exist"
47         exit 100
48 }       
49
50 if {$::tcl_platform(platform) != "windows"} {
51   proc kill {signal pid} {
52   exec kill -$signal $pid
53   }
54 } else {
55   proc kill {signal pid} {
56   exec taskkill /pid $pid /f
57   }
58 }
59         
60 package require test
61 set test::suffix ""
62 package require base64
63
64 #
65 # set  up test::src variable
66 #
67
68 if {[info exists env(TESTSRC)]} {
69         set ::test::src [file normalize $env(TESTSRC)]
70 } else {
71         set ::test::src [pwd]
72 }       
73
74 #
75 # set  up test::dir variable
76 #
77
78 if {[info exists env(TESTDIR)]} {
79         set ::test::dir [file normalize $env(TESTDIR)]
80 } else {
81         set ::test::dir [file join [pwd] z]
82 }       
83
84 #
85 # Фильтрует вывод полученный в виде длинной строки, разбивая на строки
86 # по \n. Возвращает строки, удовлетворяющие регулярному выражениу
87 # pattern
88 #
89
90 proc grep {pattern data} {
91         set out ""
92         foreach line [split $data "\n"] {
93                 if {[regexp $pattern $line]} {
94                         append out $line "\n"
95                 }
96         }       
97         return $out
98 }       
99 proc check_builtin_engine {} {
100         global OPENSSL_APP
101         set found [regexp Cryptocom [exec $OPENSSL_APP engine 2> /dev/null]]
102         if {$found} {
103                 puts "Using statically compiled engine"
104         } else {
105                 puts "Using dynamically loaded engine"
106         }
107         return $found
108 }       
109         
110
111 # Вызывает команду openssl.
112 # Посылает в лог вывод на stdout и на stderr, возвращает его же.
113 proc openssl {cmdline} {
114         global ENGINE_PATH OPENSSL_APP
115         log_vars OPENSSL_CONF CRYPT_PARAMS RNG RNG_PARAMS CCENGINE_LICENSE
116         if {[info exists ::test::engine]} {
117                 set cmdline [concat [lrange $cmdline 0 0] [list -engine $::test::engine] [lrange $cmdline 1 end]]
118         }       
119         log "OpenSSL cmdline: $OPENSSL_APP $cmdline"
120         set f [open "|$OPENSSL_APP $cmdline" r]
121         set output [read $f]
122         if {[catch {close $f} msg]} {
123                 append output "STDERR CONTENTS:\n$msg"
124                 log $output
125                 if {[lindex $::errorCode 0]!="NONE"} {
126                         return -code error -errorcode $::errorCode $output
127                 }
128         }       
129         return $output
130 }       
131
132
133 proc getConfig {args} {
134         global OPENSSL_CONF
135         if {![info exists OPENSSL_CONF]} {
136           if {![regexp "OPENSSLDIR: \"\[^\"\]+\"" [openssl version -d] => openssl_dir]} {
137                 puts stderr "Cannot find out openssl directory"
138                 exit 1
139           }
140          set OPENSSL_CONF  "$openssl_dir/openssl.cnf"
141         }
142         set f [open $OPENSSL_CONF r]
143         set out ""
144         set mode copy
145         while {[gets $f line]>=0} {
146                 if {[regexp     "\\s*\\\[\\s*(\\S+)\\s*\\\]" $line => section]} {
147                         if {[lsearch -exact $args $section]!=-1} {
148                                 set mode skip
149                         } else {
150                                 set mode copy
151                         }
152                 }
153                 if {$mode eq "copy"} {
154                         append out $line \n
155                 }       
156          }      
157          return $out
158 }        
159 #
160 # Создает тестовый CA
161 # Допустимые параметры: 
162 # CAname - директория, в которой создается CA (testCA по умолчанию)
163 # алгоритм с параметрами в формате команды req
164 #
165
166 proc makeCA {{CAname {}} {algor_with_par gost2012_512:A}} {
167         global OPENSSL_CONF
168         if {![string length $CAname]} {
169                 set CAname [file rootname [file tail $::argv0]]CA-2012
170         }       
171         set test::ca $CAname
172         file delete -force $CAname
173         file mkdir $CAname
174         makeFile $CAname/ca.conf "
175 \[ ca \]
176 default_ca      = CA_default            # The default ca section
177
178 \[ CA_default \]
179
180 dir            = [file join [pwd] $CAname]              # top dir
181 database       = \$dir/index.txt        # index file.
182 new_certs_dir  = \$dir/newcerts         # new certs dir
183
184 certificate    = \$dir/cacert.pem       # The CA cert
185 serial         = \$dir/serial           # serial no file
186 private_key    = \$dir/private/cakey.pem# CA private key
187 RANDFILE       = \$dir/private/.rand    # random number file
188
189 default_days   = 3650                  # how long to certify for
190 default_crl_days= 30                   # how long before next CRL
191 default_md     = default               # use digest corresponding the algorithm
192 default_startdate = 060101000000Z
193
194 policy         = policy_any            # default policy
195 email_in_dn    = yes                   #  add the email into cert D
196
197
198 nameopt        = ca_default            # Subject name display option
199 certopt        = ca_default            # Certificate display option
200 copy_extensions = copy                 # Copy extensions from requ
201
202
203 \[ policy_any \]
204 countryName            = supplied
205 stateOrProvinceName    = optional
206 organizationName       = optional
207 organizationalUnitName = optional
208 commonName             = supplied
209 emailAddress           = supplied
210
211 "       
212         makeFile $CAname/req.conf "
213 \[req\]
214 prompt=no
215 distinguished_name = req_dn
216 \[ req_dn \]
217 C = RU
218 L = Moscow
219 CN=Test CA $algor_with_par
220 O=Cryptocom
221 OU=OpenSSL CA
222 emailAddress = openssl@cryptocom.ru
223 "
224         file mkdir $CAname/private
225         file mkdir $CAname/newcerts
226         generate_key [keygen_params $algor_with_par] $CAname/private/cakey.pem
227         openssl "req -new  -x509 -key $CAname/private/cakey.pem -nodes -out $CAname/cacert.pem -config $CAname/req.conf -set_serial 0x11E"
228         makeFile ./$CAname/.rand 1234567890
229         makeFile ./$CAname/serial 011E
230         makeFile ./$CAname/index.txt ""
231         return [file isfile $CAname/cacert.pem]
232 }
233
234 proc extract_oids {filename {format PEM} {offset 0}} {
235         set out ""
236         if {$offset} {
237                 set miscargs "-offset $offset "
238         } else {
239                 set miscargs ""
240         }       
241         foreach line [split [openssl "asn1parse $miscargs-in $filename -inform $format -oid oidfile"] "\n"] {
242                 if {([regexp {Gost\d+} $line]||[regexp "GostR" $line]||[regexp "GOST" $line]||[regexp "sha1" $line]) && ![regexp ^Loaded: $line]} {
243                         regsub {[^:]+:[^:]+:} $line "" line
244                         append out $line "\n"
245                 }
246         }
247         return $out
248 }
249
250 # Формирует список параметров для openssl req необходимый для формирования 
251 # ключа c указанным алгоритмом и параметрами
252 #  
253 proc keygen_params {alg} {      
254         return [split $alg :] 
255 }       
256
257 proc generate_key {params filename} {
258         set alg [lindex $params 0]
259         set param [lindex $params 1]
260         set keyname $alg
261         set keyname [append keyname _ $param .pem] 
262         switch -glob $alg {
263         rsa { 
264                 if {![string length $param]} {
265                         set param 1024
266                         set keyname "rsa_1024.pem"
267                 }
268                 set optname "-algorithm rsa -pkeyopt rsa_keygen_bits:$param"
269                 }
270         ec {set optname "-paramfile $param"}
271         dsa {set optname "-paramfile $param" }
272         gost* { set optname "-algorithm $alg -pkeyopt paramset:$param" }
273         }       
274         if {$::tcl_platform(platform) eq "windows"} {
275                 set exesuffix ".exe"
276         } else {
277                 set exesuffix ""
278         }
279         log "Keyname is $keyname"
280 #       if {[engine_name] eq "open"} {
281                 log "Calling openssl cmd to create private key"
282                 openssl "genpkey  $optname -out $filename"
283 #       } elseif {[info exists ::env(OBJ)] && [file executable ../$::env(OBJ)/keytest$exesuffix]&& $alg eq "gost2001"} {
284 #               log "keytest$exesuffix $alg $param $filename"
285 #               exec ../$::env(OBJ)/keytest$exesuffix $alg $param $filename >&@ stdout
286 #       } elseif {[info exists ::env(OBJ)] && [file executable ../$::env(OBJ)/keytest$exesuffix]&& $alg eq "gost2012_256"} {
287 #               log "keytest$exesuffix $alg $param $filename"
288 #               exec ../$::env(OBJ)/keytest$exesuffix $alg $param $filename >&@ stdout
289 #       } elseif {[info exists ::env(OBJ)] && [file executable ../$::env(OBJ)/keytest$exesuffix]&& $alg eq "gost2012_512"} {
290 #               log "keytest$exesuffix $alg $param $filename"
291 #               exec ../$::env(OBJ)/keytest$exesuffix $alg $param $filename >&@ stdout
292 #       } elseif {[info exists ::env(PRIVATEKEYSDIR)] && [file exists $::env(PRIVATEKEYSDIR)/$keyname]} {
293 #               log "Copying file $keyname"
294 #               file copy $::env(PRIVATEKEYSDIR)/$keyname $filename
295 #       } else {
296 #               log "Calling openssl cmd to create private key"
297 #               openssl "genpkey  $optname -out $filename"
298 #       }
299 }
300
301 #
302 # Создает тестового пользователя с одним ключом подписи и одной заявкой
303 # на сертификат. 
304 # Параметры 
305 # username Имя директории, куда складывать файлы этого пользователя
306 # alg Параметр для опции -newkey команды openssl req, задающий алгоритм
307 #  ключа и параметры этого алгоритма
308 # Последующие параметры имеют вид списка ключ значение и задают поля
309 # Distinguished Name 
310 # FIXME Процедуру надо поправить, чтобы работала с новой версией openssl
311 proc makeUser {username alg args} {
312         file delete -force $username
313         file mkdir $username
314         if {[lsearch $args CN]==-1} {
315                 lappend args CN $username
316         }       
317         makeFile $username/req.conf [eval makeConf $args]
318         log "req.conf --------\n[getFile $username/req.conf]-------------"
319         
320         generate_key [keygen_params $alg] $username/seckey.pem
321         openssl "req -new -key $username/seckey.pem -nodes -out $username/req.pem -config $username/req.conf"
322         return [expr {[file size $username/req.pem] > 0}]
323 }
324
325 proc makeSecretKey {username alg} {
326         file delete -force $username
327         file mkdir $username
328         generate_key [keygen_params $alg] $username/seckey.pem  
329         return [expr {[file size $username/seckey.pem] > 0}]
330 }
331
332 #
333 # Создает пользователя с помощью makeUser и подписывает его сертификат
334 # ключом ранее созданного testCA. 
335 # Параметр CAname обрабатывается специальным образом: он не попадает в DN
336 #
337 proc makeRegisteredUser {username alg args } {
338         if {![info exists params(CAname)]&&![info exists ::test::ca]} {
339                 return -code error "Default CA name is not known. Have you called makeCA earlier in this script?"
340         }       
341         set CAname $test::ca
342         array set params $args
343         if {[info exist params(CAname)]} {
344                 set CAname $params(CAname)
345                 unset params(CAname)
346         }
347         if {![file isdirectory $CAname]||![file exists $CAname/cacert.pem]} {
348                 return -code error "CA $CAname doesn't exists"
349         }       
350         eval makeUser [list $username $alg] [array get params]
351         openssl "ca -config $CAname/ca.conf -in $username/req.pem -out $username/cert.pem -batch -notext" 
352         return [file isfile $username/cert.pem]
353 }
354
355 proc makeConf {args} {
356         global OPENSSL_CONF
357         array set dn_attrs [list C  RU\
358         L  Moscow\
359         CN "Dummy user"\
360         O Cryptocom\
361         OU "OpenSSL Team"\
362         emailAddress  "openssl@cryptocom.ru"\
363         ]
364         array set dn_attrs $args
365         if {[info exists dn_attrs(extensions)]} {
366                 set extensions $dn_attrs(extensions)
367                 unset dn_attrs(extensions)
368         }       
369         set out ""
370         append out {[req]
371 prompt=no
372 distinguished_name = req_dn
373 }
374 if {[info exists extensions]} {
375         append out "req_extensions = req_exts\n\[ req_exts \]\n" $extensions "\n"
376 }       
377 append out "\[ req_dn \]\n"
378         foreach {key val} [array get dn_attrs] {
379                 append out "$key=$val\n"
380         }
381         return $out
382 }       
383 #
384 # Выполняет замену регулярного выражения re на строку s в указанном
385 # PEM-документе.
386 #
387 proc hackPem {re pem s} {
388         set out ""
389         foreach {whole_pem start_line coded_body end_line} [regexp -inline -all "(-----BEGIN \[^\n\]+-----\n)(.*?)(\n-----END \[^\n\]+-----\n)" $pem] {
390                 set der [::base64::decode $coded_body]
391                 set der [regsub -all $re $der $s]
392                 append out $start_line [::base64::encode $der] $end_line
393         }
394         return $out
395 }       
396
397 #
398 # Handling of OIDs
399 #
400
401 source [file dirname  [info script]]/name2oid.tcl
402 foreach {name oid} [array get name2oid] {
403         set oid2name($oid) $name
404 }
405
406 proc long_name_by_id {id} {
407         variable name2oid
408         variable oid2name
409         if {[regexp {^\d+(\.\d+)+$} $id]} {
410         return "GOST $oid2name($id) $id"
411         }
412         return "GOST $id $name2oid($id)"
413 }
414
415 proc alg_id {alg} {
416         switch -glob $alg {
417                 gost94cc {return pk_sign94_cc}
418                 gost94cc:* {return pk_sign94_cc}
419                 gost94:* {return pk_sign94_cp}
420                 gost2001cc:* {return pk_sign01_cc}
421                 gost2001cc {return pk_sign01_cc}
422                 gost2001:* {return pk_sign01_cp}
423                 gost2012_256:* {return pk_sign12_256}
424                 gost2012_512:* {return pk_sign12_512}
425         }
426 }
427
428 proc alg_with_digest {alg} {
429         variable name2oid
430         switch -glob $alg {
431                 gost94cc {return hash_with_sign94_cc}
432                 gost94cc:* {return hash_with_sign94_cc}
433                 gost94:* {return hash_with_sign94_cp}
434                 gost2001cc:* {return hash_with_sign01_cc}
435                 gost2001cc {return hash_with_sign01_cc}
436                 gost2001:* {return hash_with_sign01_cp}
437                 gost2012_256:* {return hash_with_sign12_256}
438                 gost2012_512:* {return hash_with_sign12_512}
439                 
440         }
441 }
442
443 proc alg_long_name {alg} {
444         variable name2oid
445         switch -glob $alg {
446                 #gost94cc {return hash_with_sign94_cc}
447                 #gost94cc:* {return hash_with_sign94_cc}
448                 #gost94:* {return hash_with_sign94_cp}
449                 #gost2001cc:* {return hash_with_sign01_cc}
450                 #gost2001cc {return hash_with_sign01_cc}
451                 gost2001:* {return "GOST R 34.10-2001"}
452                 gost2012_256:* {return "GOST R 34.10-2012 with 256 bit modulus"}
453                 gost2012_512:* {return "GOST R 34.10-2012 with 512 bit modulus"}
454         }
455 }
456
457 # Returns hash algorithm corresponded to sign algorithm
458 proc alg_hash {alg} {
459     switch -glob $alg {
460         gost2012_256:* {return hash_12_256}
461         gost2012_512:* {return hash_12_512}
462         * {return hash_94}
463    }
464 }
465
466 # Returns short name of hash algorithm
467 proc hash_short_name {hash_alg} {
468     switch -glob $hash_alg {
469         *hash_94 {return md_gost94}
470         hash_12_256 {return md_gost12_256}
471         hash_12_512 {return md_gost12_512}
472         default {return $hash_alg}
473     }
474 }
475
476 proc ts_hash_long_name {hash_alg} {
477     switch -glob $hash_alg {
478         *hash_94 {return md_gost94}
479         hash_12_256 {return md_gost12_256}
480         hash_12_512 {return md_gost12_512}
481         default {return $hash_alg}
482     }
483 }
484
485 # Returns long name of hash algorithm
486 proc hash_long_name {hash_alg} {
487     switch -glob $hash_alg {
488                 *hash_94* {return "GOST R 34.11-94"}
489                 gost2001* {return "GOST R 34.11-94"}
490         *12_256* {return "GOST R 34.11-2012 with 256 bit hash"}
491         *12_512* {return "GOST R 34.11-2012 with 512 bit hash"}
492         default {return $hash_alg}
493     }
494 }
495
496 # Returns long name of hash_with_sign algorithm
497 proc hash_with_sign_long_name {alg} {
498     switch -glob $alg {
499         gost2001:* {return "GOST R 34.11-94 with GOST R 34.10-2001"}
500         gost2012_256:* {return "GOST R 34.10-2012 with GOST R 34.11-2012 (256 bit)"}
501         gost2012_512:* {return "GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)"}
502         default {return $alg}
503     }
504 }
505
506 proc smime_hash_with_sign_long_name {alg} {
507     switch -glob $alg {
508         hash_with_sign01_cp {return "GOST R 34.11-94 with GOST R 34.10-2001"}
509         hash_with_sign12_256 {return "GOST R 34.10-2012 with GOST R 34.11-2012 (256 bit)"}
510         hash_with_sign12_512 {return "GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)"}
511         default {return $alg}
512     }
513 }
514
515 proc micalg {hash_alg} {
516     switch -exact $hash_alg {
517         hash_94 {return "gostr3411-94"}
518         hash_12_256 {return "gostr3411-2012-256"}
519         hash_12_512 {return "gostr3411-2012-512"}
520     }
521 }
522
523 proc param_pubkey {alg} {
524         variable name2oid
525         switch -exact $alg {
526                 gost94cc: {return param_pubkey94_cpa}
527                 gost94cc {return param_pubkey94_cpa}
528                 gost94:A {return param_pubkey94_cpa}
529                 gost94:B {return param_pubkey94_cpb}
530                 gost94:C {return param_pubkey94_cpc}
531                 gost94:D {return param_pubkey94_cpd}
532                 gost94:XA {return param_pubkey94_cpxcha}
533                 gost94:XB {return param_pubkey94_cpxchb}
534                 gost94:XC {return param_pubkey94_cpxchc}
535                 gost2001cc: {return param_pubkey01_cc}
536                 gost2001cc {return param_pubkey01_cc}
537                 gost2001:0 {return param_pubkey01_cptest}
538                 gost2001:A {return param_pubkey01_cpa}
539                 gost2001:B {return param_pubkey01_cpb}
540                 gost2001:C {return param_pubkey01_cpc}
541                 gost2001:XA {return param_pubkey01_cpxcha}
542                 gost2001:XB {return param_pubkey01_cpxchb}
543                 gost2012_256:0 {return param_pubkey01_cptest}
544                 gost2012_256:A {return param_pubkey01_cpa}
545                 gost2012_256:B {return param_pubkey01_cpb}
546                 gost2012_256:C {return param_pubkey01_cpc}
547                 gost2012_256:XA {return param_pubkey01_cpxcha}
548                 gost2012_256:XB {return param_pubkey01_cpxchb}
549                 gost2012_512:0 {return param_pubkey12_512_0}
550                 gost2012_512:A {return param_pubkey12_512_A}
551                 gost2012_512:B {return param_pubkey12_512_B}
552         }
553 }
554
555
556 proc param_hash_long_name {hash_alg {pk_alg {}}} {
557     # R 1323565.1.023-2018 (5.2.1.2) not recommends or forbids encoding
558     # hash oid into TC26 (2012) parameters in AlgorithmIdentifier, so
559     # this is removed.
560     # Note:
561     # Commit d47b346 reverts this behavior for 512-bit 0,A,B parameters
562     switch -glob $pk_alg {
563         gost2012_256:TC* {return}
564         gost2012_512:C {return}
565     }
566     switch -glob $hash_alg {
567         *hash_94 {return "id-GostR3411-94-CryptoProParamSet"}
568         hash_12_256 {return "GOST R 34.11-2012 with 256 bit hash"}
569         hash_12_512 {return "GOST R 34.11-2012 with 512 bit hash"}
570     }
571 }
572
573 proc pubkey_long_name {alg} {
574         variable name2oid
575         switch -glob $alg {
576                 
577                 #gost2001cc: {return param_pubkey01_cc}
578                 #gost2001cc {return param_pubkey01_cc}
579                 #gost2001:0 {return param_pubkey01_cptest}
580                 gost2001:A {return "id-GostR3410-2001-CryptoPro-A-ParamSet"}
581                 gost2001:B {return "id-GostR3410-2001-CryptoPro-B-ParamSet"}
582                 gost2001:C {return "id-GostR3410-2001-CryptoPro-C-ParamSet"}
583                 gost2001:XA {return "id-GostR3410-2001-CryptoPro-XchA-ParamSet"}
584                 gost2001:XB {return "id-GostR3410-2001-CryptoPro-XchB-ParamSet"}
585                 gost2012_256:0 {return "id-GostR3410-2001-TestParamSet"}
586                 gost2012_256:A {return "id-GostR3410-2001-CryptoPro-A-ParamSet"}
587                 gost2012_256:B {return "id-GostR3410-2001-CryptoPro-B-ParamSet"}
588                 gost2012_256:C {return "id-GostR3410-2001-CryptoPro-C-ParamSet"}
589                 gost2012_256:XA {return "id-GostR3410-2001-CryptoPro-XchA-ParamSet"}
590                 gost2012_256:XB {return "id-GostR3410-2001-CryptoPro-XchB-ParamSet"}
591                 gost2012_256:TCA {return "GOST R 34.10-2012 (256 bit) ParamSet A"}
592                 gost2012_256:TCB {return "GOST R 34.10-2012 (256 bit) ParamSet B"}
593                 gost2012_256:TCC {return "GOST R 34.10-2012 (256 bit) ParamSet C"}
594                 gost2012_256:TCD {return "GOST R 34.10-2012 (256 bit) ParamSet D"}
595                 #gost2012_512:0 {return param_pubkey12_512_0}
596                 gost2012_512:A {return  "GOST R 34.10-2012 (512 bit) ParamSet A"}
597                 gost2012_512:B {return  "GOST R 34.10-2012 (512 bit) ParamSet B"}
598                 gost2012_512:C {return  "GOST R 34.10-2012 (512 bit) ParamSet C"}
599         }
600 }
601
602 proc mkObjList {args} {
603         set out ""
604         foreach name $args {
605                 if {$name eq {}} continue
606                 append out " OBJECT            :$name\n"
607         }
608         return $out
609 }
610
611 proc structured_obj_list {args} {
612         variable name2oid
613         set out {}
614         foreach {path name} $args {
615                 if {$name != {}} {set oid $name2oid($name)} {set oid {}}
616                 lappend out "$path=$oid"
617         }
618         return $out
619 }
620
621 proc param_hash {alg} {
622     switch -glob $alg {
623         gost2012_256:* {return hash_12_256}
624         gost2012_512:* {return hash_12_512}
625         * {return param_hash_94}
626     }
627 }
628
629
630 proc param_encr {short_name} {
631         variable name2oid
632         if {[regexp {^\d+(\.\d+)+$} $short_name]} {
633         return "$short_name"
634         }
635         switch -exact $short_name {
636                 cc_cipher_param {return param_encr_cc}
637                 {} {return param_encr_tc}
638                 cp_cipher_param_a {return param_encr_cpa}
639                 cp_cipher_param_b {return param_encr_cpb}
640                 cp_cipher_param_c {return param_encr_cpc}
641                 cp_cipher_param_d {return param_encr_cpd}
642         }
643 }
644
645 proc encr_long_name {short_name} {
646         variable name2oid
647         switch -exact $short_name {
648                 "1.2.643.2.2.31.1" {return "id-Gost28147-89-CryptoPro-A-ParamSet"}
649                 "1.2.643.2.2.31.2" {return "id-Gost28147-89-CryptoPro-B-ParamSet"}
650                 "1.2.643.2.2.31.3" {return "id-Gost28147-89-CryptoPro-C-ParamSet"}
651                 "1.2.643.2.2.31.4" {return "id-Gost28147-89-CryptoPro-D-ParamSet"}
652                 "1.2.643.7.1.2.5.1.1" {return "GOST 28147-89 TC26 parameter set"}
653                 {} {return "GOST 28147-89 TC26 parameter set"}
654         }
655 }
656
657
658
659 #
660 # Функции для управления клиентом и сервером при тестировании
661 # SSL-соединения
662 #
663
664 #  Параметры
665 #    Список аргументов командной строки клиента
666 #    список аргументов командной строки сервера
667 #    строка, которую надо передать на stdin клиенту
668 #
669 # Запускает openssl s_server и пытается приконнектиться к нему openssl
670 # s_client-ом. Возвращает список stdout  клиента, stderr клиента, кода
671 # завершения клиента, stdout
672 # сервера stderr сервера и кода завершения сервера.
673
674 # Если процесс убит сигналом, возвращает в качестве кода завершения имя
675 # сигнала, иначе - числовое значение кода завершения ОС
676
677 proc client_server {client_args server_args client_stdin} {
678         log "CLIENT ARGS\n$client_args\n"
679         log "SERVER ARGS\n$server_args\n"
680         flush [test_log]
681         set server [open_server $server_args]
682         set client [open_client $client_args $client_stdin]
683         log "server = $server client = $client"
684         log "Both client and server started"
685         flush [test_log]
686         global finished
687         log "Waitng for client to termintate"
688         flush [test_log]
689 #       if {$::tcl_platform(platform) == "windows"} {
690 #               exec ../kbstrike [pid $client] 0x20
691 #       }
692         vwait finished($client) 
693         catch {stop_server $server}
694         set list [concat [stop $client] [stop $server]]
695         foreach channel {"CLIENT STDOUT" "CLIENT STDERR" "CLIENT EXIT CODE"  "SERVER STDOUT"
696         "SERVER STDERR" "SERVER EXIT CODE"} data $list {
697                 log "$channel\n$data\n"
698         }
699         return $list
700 }
701 #
702 # Устанавливает командную строку для вызова клиента,
703 # в системный openssl на указанном хосте
704 #
705 proc remote_client {host} {
706         if {[info hostname] == "$host"} {
707                 set ::test::client_unset {OPENSSL_CONF}
708                 set ::test::client_app "openssl s_client"
709         } else {
710                 set ::test::client_unset {LD_LIBRARY_PATH OPENSSL_CONF}
711                 set ::test::client_app "ssh build@$host openssl s_client"
712         }
713 }       
714 #
715 # Устанавливает командную строку для вызова клиента в указанную команду
716 # Необязательный параметр указывает список переменных окружения, которые
717 # НЕ НАДО передавать в эту команду
718 #
719 proc custom_client {command {forbidden_vars {}}} {
720         set ::test::client_app $command
721         set ::test::client_unset $forbidden_vars
722
723 }
724 #
725 # Восстанавливает станадртую клиентскую команду
726 #
727 proc our_client {} {
728         catch {unset ::test::client_app}
729         catch {unset ::test::client_unset}
730 }       
731
732 #
733 # Закрывает файл, указанный в соответствующем file_id, возвращает
734 # элемент глобального массива output, содержимое error message от close
735 # и код завершения процесса (имя сигнала)
736 proc stop {file_id} {
737         global output
738         fconfigure $file_id -blocking yes
739         if {[catch {close $file_id} msg]} {
740                 if {[string match CHILD* [lindex $::errorCode 0]]} {
741                         set status [lindex $::errorCode 2]
742                 } else {
743                         set status 0
744                 }       
745         }  else {
746                 set status 0
747         }       
748         return [list $output($file_id) $msg $status]
749 }       
750 #
751 # Завершает работу сервера
752 #
753 proc stop_server {file_id} {
754 #       puts $file_id "Q\n" 
755 #       catch {set xx [socket localhost 4433]}
756         log "Interrupting process [pid $file_id]"
757         flush [test_log]
758         kill INT [pid $file_id]
759         #puts -nonewline stderr "Waiting for server termination.."
760         vwait finished($file_id)
761         if [info exists xx] {close $xx}
762 #       puts stderr "Ok"
763 }       
764
765 #
766 # Запускает процесс с указанной командной строкой. Возвращает дескриптор
767 # файла в nonblocking mode с повешенным туда fileevent
768 # Очищает соответствующие элементы массивов output и finished
769 proc start_process {cmd_line read_event {mode "r"}} {
770         set f [open "|$cmd_line" $mode]
771         global output finished
772         catch {unset finished($f)}
773         fconfigure $f -buffering none -blocking n
774         set output($f) ""
775         fileevent $f readable [list $read_event $f]
776         return $f
777 }       
778 #
779 # Обработчик fileevent-ов на чтение. Записывает считанные данные в
780 # элемент массива output соответствущий файлхендлу. В случае если
781 # достигнут eof, выставляет элемент массива finished. (элемент output
782 # при этом тоже трогается, чтобы vwait завершился)
783 #
784 proc process_read {f} {
785         global output
786         if {[eof $f]} {
787                 global finished
788                 fconfigure $f -blocking y
789                 set finished($f) 1
790                 append output($f) ""
791                 return
792         }       
793         append output($f) [read $f]
794 }       
795
796 #
797 #  Запускает openssl s_server с указанными аргументами и дожидается пока
798 #  он скажет на stdout ACCEPT. Возвращает filehandle, открытый на
799 #  чтение/запись
800 #
801 proc open_server {server_args} {
802         global OPENSSL_APP
803         global ENGINE_PATH
804         if {[info exists ::test::server_conf]} {
805                 global env
806                 set save_conf $env(OPENSSL_CONF)
807                 set env(OPENSSL_CONF) $::test::server_conf
808         }
809         if {[info exists ::test::server_app]} {
810                 set server $::test::server_app
811         } else {
812                 set server [list $OPENSSL_APP s_server]
813         }
814         if {[info exists ::test::server_unset]} {
815                 save_env $::test::server_unset
816         }       
817         set server [start_process [concat $server $server_args] process_read "r+"]
818         restore_env
819         if {[info exists save_conf]} {
820                 set env(OPENSSL_CONF) $save_conf
821         }       
822
823         global output finished
824         #puts -nonewline stderr  "Waiting for server startup..."
825         while {![regexp "\nACCEPT\n" $output($server)]} {
826                 vwait output($server)
827                 if {[info exists finished($server)]} {
828                         #puts stderr "error"
829                         return -code error [lindex  [stop $server] 1]
830                 }       
831         }               
832         #puts stderr "Ok"
833         after 100
834         return $server
835 }
836 #
837 # Сохраняет указанные переменные среды для последующего восстановления
838 # restore_env
839 #
840 proc save_env {var_list} {
841         catch {array unset ::test::save_env}
842         foreach var $var_list {
843                 if {[info exist ::env($var)]} {
844                         set ::test::save_env($var) $::env($var)
845                         unset ::env($var)
846                 }       
847         }
848
849 }
850 proc restore_env {} {
851         if {[array exists ::test::save_env]} {
852                 array set ::env [array get ::test::save_env]
853                 array unset ::test::save_env
854         }       
855         
856 }
857 #
858 # Сохраняет указанные переменные среды для последующего восстановления
859 # restore_env2. В отличие от save_env, не делает unset сохраненной переменной.
860 #
861 proc save_env2 {var_list} {
862         catch {array unset ::test::save_env2}
863         foreach var $var_list {
864                 if {[info exist ::env($var)]} {
865                         set ::test::save_env2($var) $::env($var)
866                 }       
867         }
868
869 }
870 #
871 # Восстанавливает переменные среды, ранее сохраненные функцией save_env2 
872 # В отличие от функции restore_env, требует списка переменных и 
873 # восстанавливает только переменные из данного списка. Второе отличие -
874 # если переменная из списка не была сохранена, делает ей unset.
875 #
876 proc restore_env2 {var_list} {
877         foreach var $var_list {
878                 if {[info exist ::test::save_env2($var)]} {
879                         set ::env($var) $::test::save_env2($var)
880                 } else {
881                         catch {unset ::env($var)}
882                 }
883         }
884         array unset ::test::save_env2
885 }
886
887
888 #
889 # Запускает s_client с указанными аргументами, передавая на stdin
890 # указанную строку
891 #
892 proc open_client {client_args client_stdin} {
893         global OPENSSL_APP
894         if [info exists ::test::client_app] {
895                 set client $::test::client_app
896         } else {
897                 set client [list $OPENSSL_APP s_client]
898         }
899         if {[info exists ::test::client_unset]} {
900                 save_env $::test::client_unset
901         }       
902         if {[info exists ::test::client_conf]}  {
903                 set save_env(OPENSSL_CONF) $::env(OPENSSL_CONF)
904                 set ::env(OPENSSL_CONF) $::test::client_conf
905         }
906         set client [start_process [concat $client $client_args [list << $client_stdin]] process_read]
907         restore_env
908         return $client
909 }       
910 #
911 # Зачитывает список хостов из ../../ssl-ciphers
912 #
913 proc get_hosts {file} {
914         set ::test::suffix "-$file"
915         if [file readable $file.ciphers] {
916                 set f [open $file.ciphers]
917         } else {        
918                 set f [open ../../ssl-ciphers/$file.ciphers r]
919         }
920         while {[gets $f line]>=0} {
921                 if {[regexp {^\s*#} $line]} continue
922                 append data "$line\n"
923         }
924         close $f
925         global hosts
926         array set hosts $data
927 }       
928 #
929 # Регистрирует пользователся (возможно удаленном) тестовом CA, используя
930 # скрипт testca установленный в PATH на CAhost.
931 #
932
933 proc registerUserAtCA {userdir CAhost CAprefix CApath} {
934                 global OPENSSL_APP
935                 log "registerUserAtCA $userdir $CAhost $CAprefix $CApath"
936                 set f [open  $userdir/req.pem]
937                 set request [read $f]
938                 close $f
939                 set token [::http::geturl http://$CAhost/$CAprefix/$CApath\
940                 -query [::http::formatQuery request $request startdate [clock\
941                 format [expr [clock seconds]-3600] -format "%y%m%d%H%M%SZ" -gmt y]]]
942                 if {[::http::ncode $token]!=200} {
943                         return -code error "Error certifying request [::http::data $token]"
944                 }
945                 log "Got a certificate. Saving"
946                 saveCertFromPKCS7 $userdir/cert.pem [::http::data $token]
947 }
948 proc saveCertFromPKCS7 {file pkcs7} {
949                 global OPENSSL_APP
950                 log saveCertFromPCS7
951                 log "$OPENSSL_APP pkcs7 -print_certs $pkcs7"
952                 set f [open "|[list $OPENSSL_APP pkcs7 -print_certs << $pkcs7]" r]
953                 set out [open $file w]
954                 set mode 0
955                 while {[gets $f line]>=0} {
956                         if {$mode==1} {
957                                 puts $out $line
958                                 if {$line eq "-----END CERTIFICATE-----"} {
959                                         set mode 2
960                                 }
961                         } elseif {$mode==0 && $line eq "-----BEGIN CERTIFICATE-----"} {
962                                 set mode 1
963                                 puts $out $line
964                         }
965                 }       
966                 close $f
967                 close $out
968                 if {$mode !=2 } {
969                         return -code error "Cannot get certificate from PKCS7 output"
970                 }       
971 }
972 #
973 # Invokes scp and discards stderr output if exit code is 0
974 #
975 proc scp {args} {
976         if {[info exists env(SCP)]} {
977                 set scp $env(SCP)
978         } else {
979                 set scp scp
980         }       
981         if {[catch [concat exec $scp $args] msg]} {
982                 if {[string match CHIDLD* [lindex $::errorCode 0]]} {
983                         return -code error -errorcode $::errorCode  $msg
984                 }
985         }
986 }       
987
988 proc getCAAlgParams {CAhost CAprefix alg} {
989         if {$alg == "ec" || $alg == "dsa"} {
990                 set token [::http::geturl http://$CAhost/$CAprefix/$alg?algparams=1]
991                 if {[::http::ncode $token]!=200} {
992                         return -code error "Error getting algorithm parameters [::http::data $token]"
993                 }
994                 set f [open ${alg}params.pem w]
995                 puts $f [::http::data $token]
996                 close $f
997         }
998 }       
999 #
1000 # Copies CA certificate from specified CA into ca_$alg.pem
1001 # Returns name of the ca certificate or empty line if something goes
1002 # wrong and error wasn't properly detected
1003 #
1004 proc getCAcert {CAhost CApath alg} {
1005         set token [::http::geturl http://$CAhost$CApath/$alg?getroot=1]
1006         if {[::http::ncode $token]!=200} {
1007                 return -code error "Error getting root cert for $alg: [::http::data $token]"
1008         }
1009         saveCertFromPKCS7 ca_$alg.pem [::http::data $token]     
1010         return ca_$alg.pem
1011 }
1012 #
1013 # Returns decoded version of first pem object in the given file
1014 #
1015 proc readpem {filename} {
1016         set f [open $filename]
1017         fconfigure $f -translation binary
1018         set data [read $f]
1019         close $f
1020         if {[regexp -- "-----BEGIN \[^\n\]+-----\r?\n(.*\n)-----END" $data => b64]} {
1021                 set data [::base64::decode $b64]
1022         }  
1023         return $data
1024
1025 }
1026         
1027 proc der_from_pem {pem} {
1028         if {[regexp -- {^-----BEGIN ([^\n]*)-----\r?\n(.*)\r?\n-----END \1-----} $pem => => base64]} {
1029                 ::base64::decode $base64
1030         } {
1031                 error "Not a PEM:\n$pem"
1032         }
1033 }
1034
1035 proc engine_name {} {
1036         global env
1037         if {[info exists env(ENGINE_NAME)]} {
1038                 switch -exact $env(ENGINE_NAME) {
1039                         "open" {return "open"}
1040                         "gost" {return "open"}
1041                         "cryptocom" {return "ccore"}
1042                         "ccore" {return "ccore"}
1043                         default {error "Unknown engine '$env(ENGINE_NAME)'"}
1044                 }
1045         } else {
1046                 return "ccore"
1047         }
1048 }
1049
1050 proc openssl_remote {files host cmdlinex suffix} {
1051                 set hostname [exec hostname]
1052                 set workpath /tmp/$hostname/$suffix
1053                 save_env {LD_LIBRARY_PATH OPENSSL_CONF ENGINE_DIR}
1054                 exec ssh build@$host mkdir -p $workpath
1055                 foreach file $files {
1056                         exec scp -r $file build@$host:$workpath
1057                 }
1058                 exec scp ../opnssl.sh build@$host:$workpath
1059                 exec ssh build@$host chmod +x $workpath/opnssl.sh
1060                 set cmdline [string map "TESTPATH $workpath" $cmdlinex]
1061                 log "hstname: $hostname OpenSSL cmdline: $host remote_openssl $cmdline"
1062                 set f [open "| ssh build@$host $workpath/opnssl.sh $cmdline" r]
1063                 set output [read $f]
1064                 restore_env
1065                 if {[catch {close $f} msg]} {
1066                         append output "STDERR CONTENTS:\n$msg"
1067                         log $output
1068                         if {[lindex $::errorCode 0]!="NONE"} {
1069                                 return -code error -errorcode $::errorCode $output
1070                         }
1071                 }
1072                 return $output
1073 }
1074
1075 package provide ossltest 0.7