Skip to content

Commit

Permalink
Merge pull request #99 from PKISharp/allow-external-account-binding
Browse files Browse the repository at this point in the history
Allow external account binding
  • Loading branch information
glatzert authored Aug 3, 2020
2 parents 32dc6f5 + a74cb1d commit b2b3d40
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 20 deletions.
4 changes: 2 additions & 2 deletions ACME-PS/ACME-PS.psd1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@{
RootModule = 'ACME-PS.psm1'
ModuleVersion = '1.2.5'
ModuleVersion = '1.3.0'
GUID = '2DBF7E3F-F830-403A-9300-78A11C7CD00C'

CompatiblePSEditions = @("Core", "Desktop")
Expand Down Expand Up @@ -85,7 +85,7 @@
ReleaseNotes = 'Please see the release notes from the release distribution page: https://github.com/PKISharp/ACME-PS/releases'

# Prerelase
# Prerelease = 'beta'
# Prerelease = 'beta4'
} # End of PSData hashtable

} # End of PrivateData hashtable
Expand Down
42 changes: 38 additions & 4 deletions ACME-PS/functions/Account/New-Account.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ function New-Account {
.PARAMETER EmailAddresses
Contact adresses for certificate expiration mails and similar.
.PARAMETER ExternalAccountKID
The account KID assigned by the external account verification.
.PARAMETER ExternalAccountAlgorithm
The algorithm to be used to hash the external account binding.
.PARAMETER ExternalAccountMACKey
The key to hash the external account binding object (needs to be base64 or base64url encoded)
.EXAMPLE
PS> New-Account -AcceptTOS -EmailAddresses "[email protected]" -AutomaticAccountHandling
Expand Down Expand Up @@ -53,17 +62,42 @@ function New-Account {
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string[]]
$EmailAddresses
$EmailAddresses,

[Parameter(ParameterSetName = "ExternalAccountBinding", Mandatory = $true)]
[ValidateNotNull()]
[string]
$ExternalAccountKID,

[Parameter(ParameterSetName = "ExternalAccountBinding")]
[ValidateSet('HS256','HS384','HS512')]
[string]
$ExternalAccountAlgorithm = 'HS256',

[Parameter(ParameterSetName = "ExternalAccountBinding", Mandatory = $true)]
[ValidateNotNull()]
[string]
$ExternalAccountMACKey
)

$Contacts = @($EmailAddresses | ForEach-Object { if($_.StartsWith("mailto:")) { $_ } else { "mailto:$_" } });
$contacts = @($EmailAddresses | ForEach-Object { if($_.StartsWith("mailto:")) { $_ } else { "mailto:$_" } });

$payload = @{
"termsOfServiceAgreed"=$AcceptTOS.IsPresent;
"contact"=$Contacts;
"contact"=$contacts;
}

$serviceDirectory = $State.GetServiceDirectory();
$url = $serviceDirectory.NewAccount;

if($PSCmdlet.ParameterSetName -ne "ExternalAccountBinding" -and $serviceDirectory.Meta.ExternalAccountRequired) {
throw "The ACME service requires an external account to create a new ACME account. Provide `-ExternalAccount*` Parameters."
}

$url = $State.GetServiceDirectory().NewAccount;
if($PSCmdlet.ParameterSetName -eq "ExternalAccountBinding") {
$externalAccountBinding = New-ExternalAccountPayload -State $State -ExternalAccountKID $ExternalAccountKID -ExternalAccountMACKey $ExternalAccountMACKey -ExternalAccountAlgorithm $ExternalAccountAlgorithm;
$payload.Add("externalAccountBinding", $externalAccountBinding);
}

if($PSCmdlet.ShouldProcess("New-Account", "Sending account registration to ACME Server $Url")) {
$response = Invoke-SignedWebRequest -Url $url -State $State -Payload $payload -SuppressKeyId -ErrorAction 'Stop'
Expand Down
2 changes: 2 additions & 0 deletions ACME-PS/internal/classes/AcmeDirectory.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ class AcmeDirectoryMeta {
$this.CaaIdentites = $obj.CaaIdentities;
$this.TermsOfService = $obj.TermsOfService;
$this.Website = $obj.Website;
$this.ExternalAccountRequired = $obj.ExternalAccountRequired;
}

[string[]] $CaaIdentites;
[string] $TermsOfService;
[string] $Website;
[bool] $ExternalAccountRequired;
}
18 changes: 18 additions & 0 deletions ACME-PS/internal/functions/ConvertFrom-UrlBase64.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
function ConvertFrom-UrlBase64 {
param(
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[ValidateNotNull()]
[string] $InputText
)

process {
$base64 = $InputText.Replace('-','+');
$base64 = $base64.Replace('_', '/');

while($base64.Length % 4 -ne 0) {
$base64 += '='
}

return [Convert]::FromBase64String($base64);
}
}
51 changes: 51 additions & 0 deletions ACME-PS/internal/functions/New-ExternalAccountPayload.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
function New-ExternalAccountPayload {
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNull()]
[ValidateScript({$_.AccountKeyExists()})]
[AcmeState]
$State,

[Parameter(ParameterSetName = "ExternalAccountBinding", Mandatory = $true)]
[ValidateNotNull()]
[string]
$ExternalAccountKID,

[Parameter(ParameterSetName = "ExternalAccountBinding")]
[ValidateSet('HS256','HS384','HS512')]
[string]
$ExternalAccountAlgorithm = 'HS256',

[Parameter(ParameterSetName = "ExternalAccountBinding", Mandatory = $true)]
[ValidateNotNull()]
[string]
$ExternalAccountMACKey
)

process {
$macKeyBytes = ConvertFrom-UrlBase64 $ExternalAccountMACKey;
$macAlgorithm = switch ($ExternalAccountAlgorithm) {
"HS256" { [Security.Cryptography.HMACSHA256]::new($macKeyBytes); break; }
"HS384" { [Security.Cryptography.HMACSHA384]::new($macKeyBytes); break; }
"HS512" { [Security.Cryptography.HMACSHA512]::new($macKeyBytes); break; }
}

$eaHeader = @{
"alg" = $ExternalAccountAlgorithm;
"kid" = $ExternalAccountKID;
"url" = $url;
} | ConvertTo-Json -Compress | ConvertTo-UrlBase64
$eaPayload = $State.GetAccountKey().ExportPublicJwk() | ConvertTo-Json -Compress | ConvertTo-UrlBase64;

$eaHashContent = [Text.Encoding]::ASCII.GetBytes("$($eaHeader).$($eaPayload)");
$eaSignature = (ConvertTo-UrlBase64 -InputBytes $macAlgorithm.ComputeHash($eaHashContent));

$externalAccountBinding = @{
"protected" = $eaHeader;
"payload" = $eaPayload;
"signature" = $eaSignature;
};

return $externalAccountBinding;
}
}
4 changes: 3 additions & 1 deletion ACME-PS/internal/functions/New-SignedMessage.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ function New-SignedMessage {

$signedPayload = @{};

$signedPayload.add("header", $null);
$signedPayload.add("header", $null); # TODO what does this line exist?
$signedPayload.add("protected", (ConvertTo-UrlBase64 -InputText $jsonHeaders));

if($null -eq $messagePayload -or $messagePayload.Length -eq 0) {
$signedPayload.add("payload", "");
} else {
$signedPayload.add("payload", (ConvertTo-UrlBase64 -InputText $messagePayload));
}

$signedPayload.add("signature", (ConvertTo-UrlBase64 -InputBytes $SigningKey.Sign("$($signedPayload.Protected).$($signedPayload.Payload)")));

$result = $signedPayload | ConvertTo-Json;
Expand Down
2 changes: 1 addition & 1 deletion ACME-PS/tests/A-Manual-Test-Run.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ try {
$ModuleBase = Split-Path -Path $PSScriptRoot -Parent

Remove-Module ACME-PS -ErrorAction Ignore
Import-Module "$ModuleBase\ACME-PS.psd1" -ErrorAction 'Stop'
Import-Module "$ModuleBase\ACME-PS.psd1" -Force -ErrorAction 'Stop'

Invoke-Pester -Path "$ModuleBase\tests"
}
Expand Down
26 changes: 26 additions & 0 deletions ACME-PS/tests/New-ExternalAccountPayload.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
InModuleScope "ACME-PS" {
Describe "UnitTesting New-ExternalAccountPayload" -Tag "UnitTest" {
$simpleState = Get-State -Path "$PSScriptRoot\states\simple";
$state = New-State -WarningAction 'SilentlyContinue';

$state.Set($simpleState.GetServiceDirectory())
$state.SetNonce($simpleState.GetNonce());
$state.Set($simpleState.GetAccountKey());

$result = New-ExternalAccountPayload -State $State `
-ExternalAccountKID "myKID" -ExternalAccountAlgorithm "HS256" `
-ExternalAccountMACKey "SLrdl4skg66W0NxZMwwAKPSvDtin-41SCweDRDBxMSSyh5AyoL1mNva6IMhFP13uyOQv5RI40WnnvzyXGlp77w"
It 'Returns an Object' {
$result | Should -Not -BeNullOrEmpty;
}
It 'Contains protected' {
$result.ContainsKey("protected") | Should -BeTrue;
}
It 'Contains payload' {
$result.ContainsKey("payload") | Should -BeTrue;
}
It 'Contains signature' {
$result.ContainsKey("signature") | Should -BeTrue;
}
}
}
15 changes: 15 additions & 0 deletions ACME-PS/tests/UrlBase64.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
InModuleScope ACME-PS {
Context 'ConvertTo-Base64Url and ConvertFrom-Base64 url are roundtrippable' {
$value = [byte[]]@(131,251,190,1);

$base64Form = ConvertTo-UrlBase64 -InputBytes $value;
It 'Converted the input successfully' {
$base64Form | Should -Be "g_u-AQ";
}

$roundtripped = ConvertFrom-UrlBase64 $base64Form;
It 'Should match the orginial array' {
$roundtripped | Should -Be $value;
}
}
}
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ Download the Module via Powershell-Gallery <https://www.powershellgallery.com/pa

This is a list of breaking changes, that occured during ongoing development of the module

### Version 1.3

Version 1.3 will remove the DefaultCommandPrefix from the module and prefix all Cmdlets statically with "ACME".
This will not affect you, if you did not specifically load the module with `Import-Module -Prefix myPrefix`.

### Version 1.2

Version 1.2 now automatically includes chain certificates, if they are issued by the CA.
Expand Down
4 changes: 2 additions & 2 deletions dist/ACME-PS/ACME-PS.psd1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@{
RootModule = 'ACME-PS.psm1'
ModuleVersion = '1.2.5'
ModuleVersion = '1.3.0'
GUID = '2DBF7E3F-F830-403A-9300-78A11C7CD00C'

CompatiblePSEditions = @("Core", "Desktop")
Expand Down Expand Up @@ -85,7 +85,7 @@
ReleaseNotes = 'Please see the release notes from the release distribution page: https://github.com/PKISharp/ACME-PS/releases'

# Prerelase
# Prerelease = 'beta'
# Prerelease = 'beta4'
} # End of PSData hashtable

} # End of PrivateData hashtable
Expand Down
Loading

0 comments on commit b2b3d40

Please sign in to comment.