Сегодня анонсировали возможность получения бесплатного wildcard-сертификата от Let’s Encrypt сроком на 3 месяца — ACME v2 and Wildcard Certificate Support is Live . Технические подробности можно узнать – ACME v2 Production Environment & Wildcards.
- Требуется повторная проверка домена для ACME v2
- Учетные записи для V1 или V2 staging environment(тестовых сред) не будут работать в рабочей среде
- ACME v2 поддерживает не более 300 запрос в течение 3 часов
- Проверка осуществляется только с помощью DNS-01 challenge
В прошлый раз использовали модуль ACMESharp, на данный момент он не поддерживает ACME v2, но я думаю в скором времени, все будет поддерживаться. В библиотеке certes реализована поддержка ACME v2, ее и будем использовать.
Для тренировки получения сертификатов указывайте [Certes.Acme.WellKnownServers]::LetsEncryptStagingV2 , в этом случае издатель будет — Fake LE Intermediate and Root X1. Для рабочей среды – [Certes.Acme.WellKnownServers]::LetsEncryptV2.
!! Создания записи _acme-challenge.contoso.com для DNS ,в нашем примере — Windows DNS Server !!
# Path - где храним библиотеки
# Mail - почта администратора
# Domain - домен для которого получаем сертификат
# PfxPassword - пароль для Pfx
$Path = "C:\certes"
$Mail = "mailto:admin@contoso.ru"
$Domain = "*.contoso.ru"
$PfxFriendlyName = "CONTOSO_{0}" -f (Get-Date).AddMonths(3).ToString("MMyy")
$PfxPath = Join-Path $Path "wildcard_cert.pfx"
$PfxPassword = "12345678"
# Ссылки для скачивания nupkg
$Urls = (
"https://www.nuget.org/api/v2/package/Certes",
"https://www.nuget.org/api/v2/package/BouncyCastle.Crypto.dll",
"https://www.nuget.org/api/v2/package/Newtonsoft.Json"
)
# Указываем библиотеки для проверки
$Dlls = (
"$Path\lib\net45\Certes.dll",
"$Path\lib\BouncyCastle.Crypto.dll",
"$Path\lib\net45\Newtonsoft.Json.dll"
)
# Создаем папку
if( -not (Test-Path $path) ) {
New-Item -Type Directory -Path $path | Out-Null
}
Test-Path $Dlls | Where-Object {-not $_} | Foreach-Object {
$Urls | Foreach-Object {
# Скачиваем nupkg
$r = Invoke-WebRequest $_ -UseBasicParsing
$filename = Join-Path $path $r.BaseResponse.ResponseUri.Segments[-1]
[System.IO.File]::WriteAllBytes($filename,$r.Content)
# Распаковываем nupkg
Expand-Archive $filename -OutputPath $path -Force
}
}
# Удалим лишнее
Get-ChildItem $path | Where {$_.Name -ne "lib"} | Remove-Item -Force -Recurse
# Подгрузи библиотеки
$Dlls | Foreach-Object {
[System.Reflection.Assembly]::LoadFile($_) | Out-Null
}
# Создадим учетную запись
# Для тестового использования LetsEncryptStagingV2
$context = [Certes.AcmeContext]::new([Certes.Acme.WellKnownServers]::LetsEncryptV2)
$context
$account = $context.NewAccount([System.Collections.Generic.List[string]]$Mail, $true)
$account.Result
# Формируем запрос на получение wildcard
$order = $context.NewOrder([Collections.Generic.List[string]]$Domain)
$authz = $order.Result.Authorizations()
$dnsChallenge = $authz.Result.Resource().Result.Challenges
# Содержит token для DNS записи _acme-challenge.contoso.com
$dnsTxt = [Certes.ISignatureKeyExtensions]::DnsTxt($context.AccountKey,$dnsChallenge.Token)
# Создаем ресурсные записи
# Windows DNS Server
$DomainName = $Domain.Replace("*.","")
$Name = "_acme-challenge"
$record = Get-DnsServerResourceRecord -ZoneName $DomainName -Name $Name -RRType Txt
if($record)
{
$newrecord = $record.Clone()
$newrecord.RecordData.DescriptiveText = $_.RecordValue
Set-DnsServerResourceRecord -ZoneName $DomainName -NewInputObject $newrecord -OldInputObject $record
}
else
{
Add-DnsServerResourceRecord -ZoneName $DomainName -Txt -Name $Name -DescriptiveText $dnsTxt
}
# Получаем сертификат
if($dnsTxt) {
# Отправляем запрос на проверку DNS записи
$validatetatus = $authz.Result.Challenges().Result.Validate()
$validatetatus.Result
# Finalize
$csr = [Certes.Pkcs.CertificationRequestBuilder]::new()
$csr.AddName("CN=$Domain")
$order.Result.Finalize($csr.Generate())
# Скачиваем цепочку сертификатов
$certChain = $order.Result.Download()
$cert = $certChain.Result
# Экспорт в Pfx
$pfx = [Certes.CertificateChainExtensions]::ToPfx($cert,$csr.Key)
$pfxcert = $pfx.Build($PfxFriendlyName, $PfxPassword)
[System.IO.File]::WriteAllBytes($PfxPath,$pfxcert)
}
# Отзыв сертификата
# $context.RevokeCertificate($cert.Certificate.ToDer(), [Certes.Acme.Resource.RevocationReason]::KeyCompromise,$null)
не робит что-то:
PS C:\temp> .\2.ps1
PS C:\temp> .\2.ps1
HttpClient DirectoryUri AccountKey
———- ———— ———-
Certes.Acme.AcmeHttpClient https://acme-staging-v02.api.letsencrypt.org/directory Certes.Crypto.AsymmetricCipherKey
Невозможно вызвать метод для выражения со значением NULL.
C:\temp\2.ps1:43 знак:1
+ $authz = $order.Result.Authorizations()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Невозможно вызвать метод для выражения со значением NULL.
C:\temp\2.ps1:44 знак:1
+ $dnsChallenge = $authz.Result.Resource().Result.Challenges
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Проверить вручную в консоли PowerShell с выдом после каждой команды,возможно, требуется добавить дополнительные задержки.
Да это и так ясно. Валится на $authz = $order.Result.Authorizations():
PS C:\temp> $authz = $order.Result.Authorizations()
Невозможно вызвать метод для выражения со значением NULL.
строка:1 знак:1
+ $authz = $order.Result.Authorizations()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Проверить $order.Result:
PS > $order = $context.NewOrder([Collections.Generic.List[string]]$Domain)
PS > $order
Result : Certes.Acme.OrderContext
Id : 314
Exception :
Status : RanToCompletion
IsCanceled : False
IsCompleted : True
CreationOptions : None
AsyncState :
IsFaulted : False
AsyncWaitHandle : System.Threading.ManualResetEvent
CompletedSynchronously : False
PS > $authz = $order.Result.Authorizations()
PS > $authz
Result : {Certes.Acme.AuthorizationContext}
Id : 340
Exception :
Status : RanToCompletion
IsCanceled : False
IsCompleted : True
CreationOptions : None
AsyncState :
IsFaulted : False
AsyncWaitHandle : System.Threading.ManualResetEvent
CompletedSynchronously : False
Я больше трех часов искал в интернете сегодня и не мог найти ни одной позновательной статьи, как ваша.
Лично я считаю, если бы все веб-мастера так интересно делали контент, интернет
стал бы намного лучше.