【问题标题】:Sign .csr file with "openssl ca" command using Java ProcessBuilder (through either cmd.exe or openssl.exe)使用 Java ProcessBuilder(通过 cmd.exe 或 openssl.exe)使用 \"openssl ca\" 命令对 .csr 文件进行签名
【发布时间】:2022-10-01 09:42:56
【问题描述】:

我正在尝试自动化手动生成 .p12 签名证书以进行 Web 授权的过程。

KeyPair (.p12) 和 CertReq (.csr) 文件是使用 JDK 的 keytool 程序生成的。现在我需要使用 OpenSSL 使用中间证书签署 .p12,但与在 ProcessBuilderRuntime.getRuntime().exec(...) 中执行 \"keytool\" 命令不同,openssl 很难通过 Java\ 的 Process 对象工作。我不知道它有什么问题。

我需要执行的命令:

openssl ca -config ./CA_config.cnf -extensions my_client_cert -infiles ./CA_certreqs/MY_CLIENT_AAAAAA.csr

它有 3 个时刻等待用户输入并在它们之间输出文本:

  1. 输入CA_certificate.crt密码(CA_config.cnf指向的.crt文件);
  2. 签署证书 - [y/n];
  3. 提交结果 - [y/n]。

    下面提供了代码快照。大多数 vars 都被硬代码替换,以便于阅读。

    private static void signCertReqWithOpenSSL2() throws IOException {
        String command = \"openssl ca -config ./CA_config.cnf -extensions my_client_cert -infiles ./CA_certreqs/MY_CLIENT_AAAAAA.csr\"
        String[] commandSeparated = command.split(\" \");
        
        //init cmd process
        ProcessBuilder pb = new ProcessBuilder(\"cmd.exe\");
        pb.redirectErrorStream(true);
        pb.directory(new File(\"../dir1/dir2/\").getAbsoluteFile());
        pb.command(commandSeparated);
        Process process = pb.start();
        
        try (InputStream in = process.getInputStream());
             OutputStream out = process.getOutputStream()) {
            System.out.println(\"--- begin---\");
            
            readAllConsoleOutputFromBuffer(in, 80); //93 bytes actually
            
            //enter CA_certificate.crt password
            enterUserInputToOutputStream(out, caPassword);
            readAllConsoleOutputFromBuffer(in, 10); //350
            
            //sign the certificate
            enterUserInputToOutputStream(out, \"y\");
            readAllConsoleOutputFromBuffer(in, 10); //56
            
            //commit the certification
            enterUserInputToOutputStream(out, \"y\");
            readAllConsoleOutputFromBuffer(process.getInputStream(), 10); //4815
    
            System.out.println(\"--- end ---\");
        }
        process.destroy();
    }
    
    private static void enterUserInputToOutputStream(OutputStream out, String input) throws IOException {
        out.write(String.format(\"%s%n\", input).getBytes());
        out.flush();
    }
    
    //if the stream has enough text to be printed (indicating that it\'s probably ready for user input), print it
    private static void readAllConsoleOutputFromBuffer(InputStream in, int minTextSizeInBytes) throws IOException {
    //loop is made just to make it scanning the stream during some time. I know there\'re better ways
        for (int i = 0; i < 100000; i++) {
            if (in.available() > minTextSizeInBytes) {
                String line;
                BufferedReader buff = new BufferedReader(new InputStreamReader(in));
                while ((line = buff.readLine()) != null) {
                    System.out.println(line);
                }
                break;
            }
        }
    }
    

    问题: 我无法让它结束,因此它会生成一个新的 .pem 文件和/或在控制台中输出“BEGIN CERTIFICATE”文本以供我进一步处理。

    它甚至没有到达我需要输入 CA_certificate.crt 密码的第一个输入点。 充其量,我抓住了第一行输出“使用来自 ./CA_config.cnf 的配置”。

    我确定一切都设置得很好。

    • openssl 目录存在于 %PATH% 中;
    • 所有文件和文件夹都存在并且 OpenSSL 会找到它们(如果我在 CA_config.cnf 中出错或删除了执行所需的任何文件,我会在控制台输出中捕获错误,即未找到某些内容)。

    我尝试过的:

    • 忽略控制台输出(与 InputStream 交互);
    • 等待一段时间的各种方式,因此 openssl 将准备好使用来自我的输入(Thread.sleep、其他线程检查条件或睡眠、for 循环以使时间流逝等);
    • 使用 openssl.exe 作为可执行文件而不是 cmd.exe - 我重写了命令和 CA_config.cnf 中的路径,得到了与 cmd.exe 及其相对路径相同的结果。
    • 弄乱字符串和编码,以防它在读取第一行输出后以某种方式卡在行终止符上,即使我怀疑这是根本原因。

    除了将命令委派给 .bat 文件之外,任何帮助或想法如何使工作正常?也许我没有以正确的方式与 Process 对象的输入和输出流交互。

    任何帮助表示赞赏!

    操作系统:Windows 10 x64

  • 如果你使用纯 java 来做到这一点呢?我敢打赌 bouncycastle 具有所有必需的功能。另外你为什么使用cmd.exenew ProcessBuilder(\"java\", \"-version\") 等应该直接工作。可能是superuser.com/a/1258521 (cmd.exe /c)。最后,检查[Process.html#getErrorStream()],很可能有你的原因
  • 啊,你重定向错误,所以不是这样。但是如果我正确阅读文档new Pb(\'cmd1\').setCommand(\'cmd2\') 最终将命令设置为\'cmd2\' 并忽略最初的
  • @zalp 嗯...那肯定是盲目的。我很惊讶我的“keytool”命令如何运作良好。现在我得到一个错误\“\'A_config.cnf\' is not recognized as an internal or external command\”。 PB 可能在 \"-config ./CA_config.cnf\" 部分中提取了 cmd\ 的 \"/C\" 标志。所以现在我需要弄清楚如何逃避这种情况。
  • 如果您想运行cmd /c java -version,可能需要执行(\"cmd\", \"/c\", \"java -version\"),以便 cmd 将您要执行的内容视为单个命令,而不是多个命令。如果没有cmd,情况可能会有所不同,因为现在java 想知道它的论点是什么

标签: java cmd openssl inputstream processbuilder


【解决方案1】:

它甚至没有到达我需要输入 CA_certificate.crt 密码的第一个输入点。

您使用BufferedReader.readLine,它在读取行终止符(也称为行尾/EOL)时返回 - 通常是 Unix 上的 LF、Windows 上的 CRLF 或经典 Mac 上的 CR,但它接受这三个中的任何一个在任何类型的系统上——或输入流到达文件结尾/EOF,并且由于来自子进程的 stdout 或 stderr(或两者结合时)的连接是管道,因此 EOF 仅在子进程时发生进程关闭其管道末端或退出。一般来说,提示既不会被 LF/CRLF/CR 终止,也不会立即关闭或退出,因此您的代码不会读取它。但对于这个提示见下文。

等待一段时间的各种方式,因此 openssl 将准备好使用我的输入

这是不必要和无用的;连接子进程的标准输入也是一个管道并且管道被缓冲,因此即使子进程还没有准备好读取,您也可以写入至少几千字节。

你的实际问题是openssl密码提示不使用stderr(或stdout)和stdin,而是直接使用安慰ProcessBuilder 无法捕获或处理。不要试图回答这个提示,而是在你的命令中添加两个额外的参数-passin pass:&lt;value&gt;

这样做之后你能够回答 y+EOL 到(有或没有之前阅读)'sign' 和 'commit' 提示,但您可以更轻松地添加其他参数-batch 完全跳过这些提示。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-09-03
    • 2012-06-02
    • 1970-01-01
    • 1970-01-01
    • 2018-05-01
    • 2023-03-11
    • 2015-04-18
    • 2023-04-08
    相关资源
    最近更新 更多