| 1047 | |
| 1048 | #[pymethod] |
| 1049 | fn load_cert_chain(&self, args: LoadCertChainArgs, vm: &VirtualMachine) -> PyResult<()> { |
| 1050 | // Parse certfile argument (str or bytes) to path |
| 1051 | let cert_path = Self::parse_path_arg(&args.certfile, vm)?; |
| 1052 | |
| 1053 | // Parse keyfile argument (default to certfile if not provided) |
| 1054 | let key_path = match args.keyfile { |
| 1055 | OptionalArg::Present(Some(ref k)) => Self::parse_path_arg(k, vm)?, |
| 1056 | _ => cert_path.clone(), |
| 1057 | }; |
| 1058 | |
| 1059 | // Parse password argument (str, bytes-like, or callable) |
| 1060 | // Callable passwords are NOT invoked immediately (lazy evaluation) |
| 1061 | let (password_str, password_callable) = |
| 1062 | Self::parse_password_argument(&args.password, vm)?; |
| 1063 | |
| 1064 | // Validate immediate password length (limit: PEM_BUFSIZE = 1024 bytes) |
| 1065 | if let Some(ref pwd) = password_str |
| 1066 | && pwd.len() > PEM_BUFSIZE |
| 1067 | { |
| 1068 | return Err(vm.new_value_error(format!( |
| 1069 | "password cannot be longer than {PEM_BUFSIZE} bytes", |
| 1070 | ))); |
| 1071 | } |
| 1072 | |
| 1073 | // First attempt: Load with immediate password (or None if callable) |
| 1074 | let mut result = |
| 1075 | cert::load_cert_chain_from_file(&cert_path, &key_path, password_str.as_deref()); |
| 1076 | |
| 1077 | // If failed and callable exists, invoke it and retry |
| 1078 | // This implements lazy evaluation: callable only invoked if password is actually needed |
| 1079 | if result.is_err() |
| 1080 | && let Some(callable) = password_callable |
| 1081 | { |
| 1082 | // Invoke callable - exceptions propagate naturally |
| 1083 | let pwd_result = callable.call((), vm)?; |
| 1084 | |
| 1085 | // Convert callable result to string |
| 1086 | let password_from_callable = if let Ok(pwd_str) = |
| 1087 | PyUtf8StrRef::try_from_object(vm, pwd_result.clone()) |
| 1088 | { |
| 1089 | pwd_str.as_str().to_owned() |
| 1090 | } else if let Ok(pwd_bytes_like) = ArgBytesLike::try_from_object(vm, pwd_result) { |
| 1091 | String::from_utf8(pwd_bytes_like.borrow_buf().to_vec()).map_err(|_| { |
| 1092 | vm.new_type_error("password callback returned invalid UTF-8 bytes") |
| 1093 | })? |
| 1094 | } else { |
| 1095 | return Err( |
| 1096 | vm.new_type_error("password callback must return a string or bytes") |
| 1097 | ); |
| 1098 | }; |
| 1099 | |
| 1100 | // Validate callable password length |
| 1101 | if password_from_callable.len() > PEM_BUFSIZE { |
| 1102 | return Err(vm.new_value_error(format!( |
| 1103 | "password cannot be longer than {PEM_BUFSIZE} bytes", |
| 1104 | ))); |
| 1105 | } |
| 1106 | |