嗨,
当我尝试在 MS-Windows 的严格防火墙后面使用 clojure 工具下载项目依赖项时,我遇到了 PKIX 认证异常。
无法从 central 转移工件...
(https://repo1.maven.org/maven2/)
sun.security.validator.ValidatorException: PKIX 路径构建失败
sun.security.provider.certpath.SunCertPathBuilderException: unable
find valid certification path to requested target
我认为这是由于防火墙使用的自签名根证书控制的流量引发的。
以下是一些分析和道歉,由于这篇长文。
在大组织中,工作站通常会位于防火墙后面,该防火墙监控所有与互联网的流量。公司也常创建自己的自签名根证书,并将其替换为 https 流量中找到的证书,以便解密和分析数据流,实际上利用中间人攻击。
为此,用户工作站(连接源自)需要在其受信任的证书存储中安装自签名证书。在 Windows 中,这将是从工作站可用的通用证书存储库中。
Java 附带自己的证书存储,java 密钥存储库,它与工作站上可用的通用密钥存储库分开。因此,任何从防火墙通过互联网进行的 https 连接很可能因未知证书错误而失败,因为自签名证书未安装到 Java 密钥存储库中。
这对于 clojure/clj 命令行工具下载库不利,因为它无法在 Java 密钥存储库中找到自签名证书。例如,从这样的计算机尝试访问互联网时会抛出异常。
> clojure -P
Error building classpath ...
org.eclipse.aether.resolution.ArtifactDescriptorException: Failed to
read artifact descriptor for .... ... Caused by:
org.eclipse.aether.resolution.ArtifactResolutionException: Could not
transfer artifact ... from/to central
(https://repo1.maven.org/maven2/):
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target ...
...
我想至少有两种方法可以解决此问题
1. 在 Java 密钥存储库中安装缺失的证书,
2. 指示 Java 实例检查通用密钥存储库。
似乎没有直接的方法来发现缺失的证书,也许需要用户自己使用 java keytool 将它们安装到 Java 密钥存储库中(请参阅https://docs.oracle.com/cd/E19830-01/819-4712/ablqw/index.html中的证书和 SSL 工作原理)。
相反,似乎通过使用java.next.ssl.*
属性来指导 java 读取通用或备用密钥库更容易(详情请参考以下链接:https://stackoverflow.com/questions/5871279/ssl-and-cert-keystore)。
在 Windows 上,我认为大多数拥有如此严格的防火墙的用户都使用 Windows 本地密钥库,只需简单地设置一个 java 属性来指导 java 使用通用 Windows 密钥库(详情请参考以下链接:https://stackoverflow.com/questions/41257366/import-windows-certificates-to-java)
javax.net.ssl.trustStoreType=Windows-ROOT
鉴于这种情况,我原本期望在 Windows 上以下代码能够工作
clojure -J-D'javax.net.ssl.trustStoreType=Windows-ROOT' -P
但事实并非如此。结果显示$JvmOpts
变量在此 PowerShell 脚本中没有传递到预备命令中https://github.com/clojure/brew-install/blob/b91fb78e321b5e39bedda594f5d578579d448d19/src/main/resources/clojure/install/ClojureTools.psm1#L388
& $JavaCmd -classpath $ToolsCp clojure.main -m clojure.tools.deps.alpha.script.make-classpath2 --config-user $ConfigUser --config-project $ConfigProject --basis-file $BasisFile --libs-file $LibsFile --cp-file $CpFile --jvm-file $JvmFile --main-file $MainFile --manifest-file $ManifestFile @ToolsArgs
以下是我考虑使用 java 属性来解决这个问题的一些选项,从最具侵入性/最具体到通用情况
- 如果运行在 Windows 上,负责下载依赖的函数
clojure.tools.deps.alpha.script.make-classpath2
应设置javax.net.ssl.trustStoreType=Windows-ROOT
,这样就可以在运行时使用
- 例如:
(System/setProperty "javax.net.ssl.trustStoreType" "Windows-ROOT")
。
- cons
- 这仅在 Windows 上有效。
- 这会强制使用通用密钥库,这可能并非用户一直所期望的。
- 可以通过添加一个新脚本选项(例如-W)来减轻这一点,该选项将作为参数传递给
clojure.tools.deps.alpha
。缺点是用户/工具在每个调用时都必须传递此标志。
- 使用
System/setProperty
在运行时设置javax.net.ssl.tustStoreType
属性可能没有效果,如果有任何 SSL 连接是在代码的其他部分之前建立的。
- 与上一个选项相同,但引入一个新脚本选项(例如-Sssl),其中 EDN 是
javax.net.ssl.*
属性,并将其作为此类传递给clojure.tools.deps.alpha
,以便在运行时进行设置。
- 例如:
clojure -Sssl {:trustStoreType "Windows-ROOT"} -P
- cons
- 与第 1 点有相同的缺点,并且对于用户来说更加繁琐,每次调用 clojure 时都必须输入 edn 映射。
- 创造一个新的 deps.edn 键(例如:
:clojure.tools.deps.alpha/ssl
),其配对将是java.net.ssl.*
属性,可以在用户配置的 deps.edn 中设置,并由 clojure.tools.deps.alpha
的 make-classpath2
函数解析,在运行时使用 System/setProperty
设置属性。
- 例如,示例 clj 用户配置
{:clojure.tools.deps.alpha/ssl {:trustStore "Windows-ROOT"}}
- cons
- 使用
System/setProperty
在运行时设置javax.net.ssl.*
属性可能没有效果,如果有任何 SSL 连接是在代码的其他部分之前建立的。
- 更新脚本,以便将 -J JVM 选项传递到 -P 命令的 java 调用中(我已经测试过这个可以工作)。
- 例如:
clojure -J-D'javax.net.ssl.trustStoreType=Windows-ROOT' -P
- cons
- 用户/工具仍然必须在每次 clojure 调用时提供
-J-Djavax.net.ssl.*
选项。
- 与上一个选项相同,但引入一个环境变量
CLJ_JVMOPTS
,其值插入脚本中的$JvmOpts
变量。
- 例如:
set CLJ_JVMOPTS=-D'javax.net.ssl.trustStoreType=Windows-ROOT'
,然后clojure -P
我更倾向于第 5 点,因为它似乎没有任何缺点,并且可以设置一次并在所有 clojure 调用中都应用,为用户/工具提供更好的体验。
让我知道你的想法。我很乐意处理脚本和/或 tools.deps.alpha
库中的更改。
谢谢
PS:我还尝试在 MAVEN_OPTS
变量中设置属性,按照https://maven.apache.org/guides/mini/guide-repository-ssl.html 进行,但没有任何效果。我怀疑这只有在直接调用 mvn
命令行工具时才会生效。