你好,
在尝试使用clojure工具在MS-Windows的严格防火墙后下载项目依赖项时,我遇到了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
- 例如:
(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:我也尝试在MAVEN_OPTS
变量中设置属性,就像在https://maven.apache.org/guides/mini/guide-repository-ssl.html中所做的,但它没有任何效果。我怀疑这只有在直接调用mvn
命令行工具时才会生效。