﻿# 如何調整 PowerShell 的 Invoke-WebRequest 改用 TLS 1.2 協定下載檔案

記得去年(2018)二月的時候，GitHub 在[部落格](https://blog.github.com/2018-02-23-weak-cryptographic-standards-removed/)宣布正式移除所有不安全的 TLS 加密連線協定版本，骨董級的 `SSLv3` 就不用說了，老早就被廢棄。當時主要是廢棄 `TLSv1` 與 `TLSv1.1` 版本，且套用到 `github.com` 與 `api.github.com` 所有的 HTTPS 加密連線，記得當時還有不少災情傳出。你可能很難想像，使用 PowerShell 的 [Invoke-WebRequest](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest) 下載使用 HTTPS 加密連線的檔案，預設採用的加密協議竟然是 `SSLv3` 與 `TLSv1` 版本！本篇文章將分享如何調整這個預設值。

### 問題描述

不常用 PowerShell 的人，可能不太清楚，由於 PowerShell 底層使用 .NET Framewrk 為核心，而 .NET 4.0 版最高僅支援到 `TLSv1` 版本，直到 .NET 4.5 版開始才支援到 `TLSv1.2` 版本。如果你的電腦同時安裝 .NET 4.0 與 .NET 4.5 版，那麼就算你的應用程式指向到 .NET 4.0 的 Runtime，也會支援到最高 `TLSv1.2` 版。

不過，即便 `.NET 4.5` 最高支援到 `TLSv1.2` 版本，但 .NET 為了與老舊系統相容，預設值還是被設定在 `SSLv3` 與 `TLSv1` 版本，而這個設定便直接影響 PowerShell 的 Invoke-WebRequest 執行。換句話說，當你想透過 Invoke-WebRequest 下載 GitHub 上面的任何檔案，預設就會被阻擋下來。

你可以試試下載[這個](https://github.com/angular/angular-cli/archive/v7.2.1.zip)檔案：

```ps
Invoke-WebRequest https://github.com/angular/angular-cli/archive/v7.2.1.zip
```

你會立即得到以下錯誤：

```
Invoke-WebRequest : 要求已經中止: 無法建立 SSL/TLS 的安全通道。
位於 線路:1 字元:1
+ Invoke-WebRequest https://github.com/angular/angular-cli/archive/v7.2 ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest]，WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
```

### 解決方案

這個問題其實蠻好解決，調整一個 .NET Framework 的 [ServicePointManager.SecurityProtocol](https://learn.microsoft.com/zh-tw/dotnet/api/system.net.servicepointmanager.securityprotocol) 靜態屬性即可！

如果你想在 .NET 應用程式中額外加入 `TLSv1.2` 版本，可以參考以下 `C#` 程式碼範例：

```csharp
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
```

如果想移除所有舊版，重新指派版本，可以參考以下 `C#` 程式碼範例：

```csharp
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
```

但是，在 PowerShell 中該如何設定呢？請參考以下命令，他可以讓你重新指派 TLS 版本為 `TLSv1.1` 與 `TLSv1.2` 版本：

```ps
[System.Net.ServicePointManager]::SecurityProtocol = "tls12, tls11"
```

所以今後要下載 GitHub 上面的檔案時，就要改寫如下：

```ps
[System.Net.ServicePointManager]::SecurityProtocol = "tls12"
Invoke-WebRequest https://github.com/angular/angular-cli/archive/v7.2.1.zip -OutFile v7.2.1.zip
```

> 網路上有人提到可以改系統機碼 (Registry) 調整全域設定，但我建議還是不要這樣改比較安全。

### 調整 `powershell` 工作階段的預設值

要調整每一個 PowerShell 工作階段的預設值，你可以透過 `$PROFILE` 變數找到這個，如果檔案不存在，就自己建立一個。

接著再將以下這行複製進去即可：

```ps
[System.Net.ServicePointManager]::SecurityProtocol = "tls12,tls11"
```

改用另外一種寫法也可以：

```ps
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Tls12,Tls11';
```

這種寫法也可以，但這種語法只能指定一個列舉項目：

```ps
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
```

### 相關連結

-   [ServicePointManager.SecurityProtocol Property (System.Net) | Microsoft Docs](https://learn.microsoft.com/zh-tw/dotnet/api/system.net.servicepointmanager.securityprotocol)
    -   [SecurityProtocolType Enum](https://learn.microsoft.com/zh-tw/dotnet/api/system.net.securityprotocoltype)
-   [如何在 .NET Framework 4.0, 4.5 以上的程式支援 TLS 1.2 – 慢步在雲端… 邱英瑞(Jacky)](https://blogs.msdn.microsoft.com/jchiou/2016/05/27/%E5%A6%82%E4%BD%95%E5%9C%A8-net-framework-4-0-4-5-%E4%BB%A5%E4%B8%8A%E7%9A%84%E7%A8%8B%E5%BC%8F%E6%94%AF%E6%8F%B4-tls-1-2/)
-   [Weak cryptographic standards removed | The GitHub Blog](https://blog.github.com/2018-02-23-weak-cryptographic-standards-removed/)
-   [How-to: Create and use PowerShell enums](https://ss64.com/ps/syntax-enum.html)
