@@ -275,5 +275,181 @@ def test_legacy_aes_cbc_128_roundtrip(self):
275275 "legacy aes-cbc-128 round trip failed" )
276276
277277
278+ def _camellia_available ():
279+ """Check if Camellia support is enabled in the wolfssl binary."""
280+ crl = os .path .join (CERTS_DIR , "crl.der" )
281+ if not os .path .isfile (crl ):
282+ return False
283+ probe = "test-cam-probe.enc"
284+ try :
285+ r = run_enc ("enc" , "-camellia-128-cbc" , "-in" , crl , "-out" , probe ,
286+ password = "testpass" )
287+ return r .returncode == 0
288+ finally :
289+ if os .path .exists (probe ):
290+ os .remove (probe )
291+
292+
293+ class EncStdinInputTest (unittest .TestCase ):
294+ """Regression tests for stack buffer overflow fix (scanf -> fgets).
295+
296+ When -in or -out is omitted, wolfCLU prompts for the filename on stdin.
297+ These tests exercise the fgets-based input paths, including the
298+ empty-line re-prompt and the too-long-input flush branches.
299+ """
300+
301+ @classmethod
302+ def setUpClass (cls ):
303+ if not os .path .isdir (CERTS_DIR ):
304+ raise unittest .SkipTest ("certs directory not found" )
305+
306+ config_log = os .path .join ("." , "config.log" )
307+ if os .path .isfile (config_log ):
308+ with open (config_log , "r" ) as f :
309+ if "disable-filesystem" in f .read ():
310+ raise unittest .SkipTest ("filesystem support disabled" )
311+
312+ cls .has_camellia = _camellia_available ()
313+
314+ def _cleanup (self , * files ):
315+ for f in files :
316+ self .addCleanup (lambda p = f : os .remove (p )
317+ if os .path .exists (p ) else None )
318+
319+ def _run_enc_stdin (self , stdin_data , * args , password = "testpass" ):
320+ """Run wolfssl enc with stdin input (for filename prompts)."""
321+ cmd = [WOLFSSL_BIN , "enc" ] + list (args ) + ["-k" , password ]
322+ return subprocess .run (cmd , input = stdin_data ,
323+ capture_output = True , text = True , timeout = 60 )
324+
325+ # -- AES (EVP path) --
326+
327+ def test_aes_inname_via_stdin (self ):
328+ """-in omitted; filename supplied via stdin (inName path)."""
329+ enc = "test-stdin-in.enc"
330+ dec = "test-stdin-in.dec"
331+ orig = os .path .join (CERTS_DIR , "crl.der" )
332+ self ._cleanup (enc , dec )
333+
334+ r = self ._run_enc_stdin (f"{ orig } \n " , "-aes-128-cbc" , "-out" , enc )
335+ self .assertEqual (r .returncode , 0 ,
336+ f"enc with stdin input failed: { r .stderr } " )
337+
338+ r = run_enc ("enc" , "-d" , "-aes-128-cbc" , "-in" , enc , "-out" , dec ,
339+ password = "testpass" )
340+ self .assertEqual (r .returncode , 0 , r .stderr )
341+ self .assertTrue (filecmp .cmp (orig , dec , shallow = False ),
342+ "stdin enc/dec roundtrip mismatch" )
343+
344+ def test_aes_inname_empty_line_reprompt (self ):
345+ """Empty line on stdin is rejected; next valid filename accepted."""
346+ enc = "test-empty-in.enc"
347+ dec = "test-empty-in.dec"
348+ orig = os .path .join (CERTS_DIR , "crl.der" )
349+ self ._cleanup (enc , dec )
350+
351+ r = self ._run_enc_stdin (f"\n { orig } \n " , "-aes-128-cbc" , "-out" , enc )
352+ self .assertEqual (r .returncode , 0 ,
353+ f"enc should accept filename after empty line: "
354+ f"{ r .stderr } " )
355+
356+ r = run_enc ("enc" , "-d" , "-aes-128-cbc" , "-in" , enc , "-out" , dec ,
357+ password = "testpass" )
358+ self .assertEqual (r .returncode , 0 , r .stderr )
359+ self .assertTrue (filecmp .cmp (orig , dec , shallow = False ),
360+ "enc/dec roundtrip mismatch after empty-line reprompt" )
361+
362+ def test_aes_inname_too_long_reprompt (self ):
363+ """Overlong input is flushed; next valid filename accepted."""
364+ enc = "test-toolong-in.enc"
365+ dec = "test-toolong-in.dec"
366+ orig = os .path .join (CERTS_DIR , "crl.der" )
367+ self ._cleanup (enc , dec )
368+
369+ long_input = " " * 255
370+ r = self ._run_enc_stdin (f"{ long_input } \n { orig } \n " ,
371+ "-aes-128-cbc" , "-out" , enc )
372+ self .assertEqual (r .returncode , 0 ,
373+ f"enc should recover after too-long input: "
374+ f"{ r .stderr } " )
375+
376+ r = run_enc ("enc" , "-d" , "-aes-128-cbc" , "-in" , enc , "-out" , dec ,
377+ password = "testpass" )
378+ self .assertEqual (r .returncode , 0 , r .stderr )
379+ self .assertTrue (filecmp .cmp (orig , dec , shallow = False ),
380+ "enc/dec roundtrip mismatch after too-long reprompt" )
381+
382+ # -- Camellia (non-EVP path) --
383+
384+ def test_camellia_outname_via_stdin (self ):
385+ """-out omitted; output filename supplied via stdin (non-EVP path)."""
386+ if not self .has_camellia :
387+ self .skipTest ("Camellia not available" )
388+ enc = "test-cam-stdin.enc"
389+ dec = "test-cam-stdin.dec"
390+ orig = os .path .join (CERTS_DIR , "crl.der" )
391+ self ._cleanup (enc , dec )
392+
393+ r = self ._run_enc_stdin (f"{ enc } \n " , "-camellia-128-cbc" , "-in" , orig )
394+ self .assertEqual (r .returncode , 0 ,
395+ f"Camellia enc with stdin output name failed: "
396+ f"{ r .stderr } " )
397+
398+ r = self ._run_enc_stdin (f"{ dec } \n " , "-d" , "-camellia-128-cbc" ,
399+ "-in" , enc )
400+ self .assertEqual (r .returncode , 0 ,
401+ f"Camellia dec with stdin output name failed: "
402+ f"{ r .stderr } " )
403+ self .assertTrue (filecmp .cmp (orig , dec , shallow = False ),
404+ "Camellia stdin outName roundtrip mismatch" )
405+
406+ def test_camellia_outname_empty_line_reprompt (self ):
407+ """Empty line rejected; next valid output name accepted (non-EVP)."""
408+ if not self .has_camellia :
409+ self .skipTest ("Camellia not available" )
410+ enc = "test-cam-empty.enc"
411+ dec = "test-cam-empty.dec"
412+ orig = os .path .join (CERTS_DIR , "crl.der" )
413+ self ._cleanup (enc , dec )
414+
415+ r = self ._run_enc_stdin (f"\n { enc } \n " , "-camellia-128-cbc" , "-in" , orig )
416+ self .assertEqual (r .returncode , 0 ,
417+ f"Camellia enc should accept output name after "
418+ f"empty line: { r .stderr } " )
419+
420+ r = self ._run_enc_stdin (f"\n { dec } \n " , "-d" , "-camellia-128-cbc" ,
421+ "-in" , enc )
422+ self .assertEqual (r .returncode , 0 ,
423+ f"Camellia dec should accept output name after "
424+ f"empty line: { r .stderr } " )
425+ self .assertTrue (filecmp .cmp (orig , dec , shallow = False ),
426+ "Camellia roundtrip mismatch after empty-line "
427+ "reprompt" )
428+
429+ def test_camellia_outname_too_long_reprompt (self ):
430+ """Overlong input flushed; next valid output name accepted (non-EVP)."""
431+ if not self .has_camellia :
432+ self .skipTest ("Camellia not available" )
433+ enc = "test-cam-toolong.enc"
434+ dec = "test-cam-toolong.dec"
435+ orig = os .path .join (CERTS_DIR , "crl.der" )
436+ self ._cleanup (enc , dec )
437+
438+ long_input = " " * 255
439+ r = self ._run_enc_stdin (f"{ long_input } \n { enc } \n " ,
440+ "-camellia-128-cbc" , "-in" , orig )
441+ self .assertEqual (r .returncode , 0 ,
442+ f"Camellia enc should recover after too-long output "
443+ f"name: { r .stderr } " )
444+
445+ r = self ._run_enc_stdin (f"{ long_input } \n { dec } \n " ,
446+ "-d" , "-camellia-128-cbc" , "-in" , enc )
447+ self .assertEqual (r .returncode , 0 ,
448+ f"Camellia dec should recover after too-long output "
449+ f"name: { r .stderr } " )
450+ self .assertTrue (filecmp .cmp (orig , dec , shallow = False ),
451+ "Camellia roundtrip mismatch after too-long reprompt" )
452+
453+
278454if __name__ == "__main__" :
279455 test_main ()
0 commit comments