大家好,
在使用clojure工具在Windows严格防火墙后尝试下载项目依赖项时,我遇到了PKIX证书异常。
无法从中央仓库传输工件...
(https://repo1.maven.org/maven2/)
sun.security.validator.ValidatorException: PKIX路径构建失败
sun.security.provider.certpath.SunCertPathBuilderException: unable
找不到请求目标的有效证书路径
我认为这是由于防火墙使用的自签名根证书导致的,该证书用于控制与监控流量。
以下是分析,并对较长的阅读表示歉意。
在大组织中,工作站通常位于防火墙之后,该防火墙控制和监控所有互联网的流量。公司创建自己的自签名根证书并将它们替换到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 查看通用或备选密钥存储库更容易。
在 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 时都必须始终传递此标志。
- 使用
System/setProperty
在运行时设置 javax.net.ssl.tustStoreType
属性可能没有效果,如果任何 SSL 连接之前在其他代码部分中创建过。
- 与前述内容相同,但增加一个新的脚本选项(例如 -Sssl EDN),其 EDN 是
javax.net.ssl.*
属性,并将以此形式传递给 clojure.tools.deps.alpha
以在运行时设置。
- 例如:
clojure -Sssl {:trustStoreType "Windows-ROOT"} -P
- cons
- 与前述内容相同的缺点,并且对于用户来说更繁琐,每次调用 clojure 时都必须键入 edn map。
- 创建一个新的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
变量中,如 per https://maven.apache.org/guides/mini/guide-repository-ssl.html,但这没有效果。我怀疑这只能直接调用 mvn
命令行工具时才有效果。