大家好,
当我在 MS-Windows 的严格防火墙后面使用 clojure 工具尝试下载项目依赖时,我遇到了 PKIX 证书异常。
无法从中央仓库 transfers artifact ...
(https://repo1.maven.org/maven2/)
sun.security.validator.ValidatorException: PKIX 路径构建失败
sun.security.provider.certpath.SunCertPathBuilderException: 无法
找到到请求目标的有效认证路径
我认为这是由于防火墙使用的自签名根证书所致,该证书用于控制与监视流量。
以下是分析内容,抱歉篇幅较长。
在大组织中,工作站通常位于防火墙后面,该防火墙控制并监视所有来自或前往互联网的流量。公司也会创建自己的自签名根证书,并用它替换 https 流量中找到的证书,以解密和分析数据流,从而利用中间人攻击来达到目的。
为此,发起连接的用户工作站需要在他们的受信任证书存储中安装自签名证书。在 Windows 中,这将是受信任根授权证书密钥库。
Java 自带其证书存储,即 java KeyStore,它与工作站上可用的常规密钥库分开。因此,任何通过防火墙连接到互联网的 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.*
属性(请参阅https://stackoverflow.com/questions/5871279/ssl-and-cert-keystore)来指导java查找通用或备用的密钥库更容易。
在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属性来解决问题的方案,从最具侵入性/最具体到通用方案
- 让负责下载依赖项的
clojure.tools.deps.alpha.script.make-classpath2
函数在Windows上运行时设置javax.net.ssl.trustStoreType=Windows-ROOT
,如果运行在Windows上
- .例如,
(System/setProperty "javax.net.ssl.trustStoreType" "Windows-ROOT")
。
- cons
- 这仅在Windows上有效。
- 这强迫使用通用密钥库,这可能不是用户总是想要的。
- 这可以通过添加一个新的脚本选项(例如-W)来缓解,该选项作为选项传递到
clojure.tools.deps.alpha
。缺点是用户/工具必须在每次调用时始终传递此标志。
- 使用
System/setProperty
在运行时设置javax.net.ssl.tustStoreType
属性可能没有任何效果,如果代码的其它部分之前已建立任何SSL连接。
- 与上一条相同,但使用新的脚本选项(例如-Sssl EDN),其中的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:我也尝试过按照https://maven.apache.org/guides/mini/guide-repository-ssl.html将属性设置在MAVEN_OPTS
变量中,但没有任何效果。我怀疑这只有在直接调用mvn
命令行工具时才会生效。