你好,
我在尝试使用 Clojure 工具在 MS-Windows 上通过严格的防火墙下载项目依赖时遇到了 PKIX 证书异常。
无法从/to central 转移工件...
(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中的“处理证书和 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
变量未传递给 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
,如果运行在 Windows 上。
- 例如:
(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
设置属性。
- 例如:
{: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
命令行工具时才有效。