大家好,
我在尝试使用运行在MS-Windows上严格防火墙后面的clojure工具下载项目依赖时遇到PKIX证书异常。
无法从中央传输...到中央
(https://repo1.maven.org/maven2/)
sun.security.validator.ValidatorException: PKIX路径构建失败
sun.security.provider.certpath.SunCertPathBuilderException: 不能
找到指向请求目标的有效证书路径
我认为这是由于防火墙使用的自签名根证书控制的流量引起的。
以下是分析,对于阅读的耐心表示歉意。
在大组织里,工作站通常位于防火墙后面,该防火墙控制并监控所有进出互联网的流量。公司通常还会创建自己的自签名根证书,并用它替换找到的https流量中的证书,以便解密和分析数据流,从而实际上使用中间人攻击获得优势。
为此,连接发源的用户工作站需要在他们的受信任证书存储中安装自签名证书。在Windows上,这将是受信任根权限证书密钥库。
Java带有自己的证书存储,即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上,很多用户可能使用着此类限制性防火墙,设置一个简单的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
但它并没有。其实,在这里的PowerShell脚本中,$JvmOpts
变量并未传递给预备命令(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
来设置运行时属性
- .e.g.
(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
以在运行时设置。
- e.g.
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
设置属性。
- e.g. 示例clj用户配置
{:clojure.tools.deps.alpha/ssl {:trustStore "Windows-ROOT"}}
- cons
- 使用
System/setProperty
在运行时设置javax.net.ssl.*
属性可能对之前从代码的其他部分建立的任何SSL连接没有任何影响。
- 更新脚本,以便将-J JVM选项传递到-P命令的Java调用中(我已测试过此方法有效)。
- e.g.
clojure -J-D'javax.net.ssl.trustStoreType=Windows-ROOT' -P
- cons
- 用户/工具仍然必须在每个clojure调用中提供
-J-Djavax.net.ssl.*
选项。
- 与上一个类似,但引入一个
CLJ_JVMOPTS
环境变量,其值插入到脚本的$JvmOpts
变量中。
- e.g.
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
命令行工具时生效。