你好,
当使用 clojure 工具在 MS-Windows 上的严格防火墙后尝试下载项目依赖项时,我遇到了 PKIX 证书异常。
无法从中央仓库传输munment ...
(https://repo1.maven.org/maven2/)
sun.security.validator.ValidatorException: PKIX 路径构建失败
sun.security.provider.certpath.SunCertPathBuilderException: unable
to find valid certification path to requested target
我认为这是由于防火墙用来控制和监控流量而使用的自签名根证书造成的。
以下是分析,并对此长文表示歉意。
在大组织中,工作站通常位于防火墙之后,该防火墙控制和监控所有进出互联网的流量。公司也通常创建他们自己的自签名根证书并将它们替换成 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 查看 general 或 alternative keystores。
在Windows上,我假设大多数使用如此限制性防火墙的用户都是这样的人,这仅仅是一个简单的问题,只需设置一个java属性,让java使用通用的Windows密钥库。
javax.net.ssl.trustStoreType=Windows-ROOT
鉴于这种情况,我本来期待以下操作在Windows上都能正常工作。
clojure -J-D'javax.net.ssl.trustStoreType=Windows-ROOT' -P
但事实并非如此。结果显示,这里的powershell脚本没有办法将$JvmOpts
变量传输给prep命令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
fn所负责的依赖项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),其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
fn解析,在运行时使用System/setProperty
设置属性。
- 例如,示例clj用户配置
{:clojure.tools.deps.alpha/ssl {:trustStore "Windows-ROOT"}}
- cons
- 使用
System/setProperty
在运行时设置javax.net.ssl.*
属性可能不会产生任何效果,如果之前代码的其他部分已经建立 SSL 连接。
- 更新脚本以在-P命令的java调用中传递-J jvm选项(我已经测试过它有效)。
- 例如:
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
命令行工具时才会生效。