你好,
我在使用 clojure 工具尝试在 MS-Windows 的严格要求防火墙后下载项目依赖关系时遇到了 PKIX 证书异常。
无法从 central 转移构件...
(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 (请参阅https://docs.oracle.com/cd/E19830-01/819-4712/ablqw/index.html) 将它们安装到 java 密钥存储中的一些努力。
相反,似乎更简单的是通过使用 java.next.ssl.*
属性来指示 java 读取通用或备用的密钥存储(https://stackoverflow.com/questions/5871279/ssl-and-cert-keystore)。
在 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 脚本传递给 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
以下是一些可行的选项,从最侵入性/具体到通用情况
- 如果运行在 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
。缺点是用户/工具必须在每次调用时始终传递此标志。
- 如果在代码的其他部分之前已经建立了任何 SSL 连接,则使用
System/setProperty
设置 javax.net.ssl.tustStoreType
属性可能没有效果。
- 与上一个选项相同,但是使用一个新的脚本选项(例如 -Sssl EDN),该选项的 EDN 是
javax.net.ssl.*
属性,它们将被作为此类传递给 clojure.tools.deps.alpha
以在运行时设置。
- 例如
clojure -Sssl {:trustStoreType "Windows-ROOT"} -P
- cons
- 与第 1 个选项相同的缺点,而且对于用户来说,每次调用 clojure 时都不得不键入 edn 映射,这更加繁琐。
- 发明一个新的 dep.sedn 键(例如
:clojure.tools.deps.alpha/ssl
),其值对应该是 java.net.ssl.*
属性,可以设置在用户配置 dep.sedn 中,并由 clojure.tools.deps.alpha
的 make-classpath2
函数解析,使用 System/setProperty
在运行时设置属性。
- 例如,用户配置 clj 的示例
{:clojure.tools.deps.alpha/ssl {:trustStore "Windows-ROOT"}}
- cons
- 如果在代码的其他部分之前已经建立了任何 SSL 连接,则使用
System/setProperty
在运行时设置 javax.net.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
命令行工具时才会生效。