你好,
我在尝试使用在 MS-Windows 独立防火墙上运行 clojure 工具下载项目依赖项时遇到了 PKIX 证书异常。
无法将工件 ... 从/到中央转移
(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)。
但是,似乎更容易指导 Java 使用 java.next.ssl.*
属性来查看通用或替代密钥存储库(《关于 SSL 和证书的资料》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
以下是我能想到的几种利用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
。缺点是,在每次调用 clojure 时用户/工具必须始终传递此标志。
- 如果在其他代码部分之前已经建立过任何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 映射,这使得操作更加繁琐。
- 发明一个新依赖的 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
- 如果在其他代码部分之前已经建立过任何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:我也尝试将属性设置在 MAVEN_OPTS
变量中,如 https://maven.apache.org/guides/mini/guide-repository-ssl.html 中所述,但没有任何效果。我怀疑这只有在直接调用 mvn
命令行工具时才会生效。