Skip to content

Commit

Permalink
#472: initial work for gets client certs and auth working
Browse files Browse the repository at this point in the history
  • Loading branch information
Badgerati committed Sep 22, 2020
1 parent 4b0588e commit be0484a
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 14 deletions.
48 changes: 48 additions & 0 deletions examples/web-auth-clientcert.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode

# create a server, flagged to generate a self-signed cert for dev/testing, but allow client certs for auth
Start-PodeServer {

# bind to ip/port and set as https with self-signed cert
Add-PodeEndpoint -Address * -Port 8443 -Protocol Https -SelfSigned -AllowClientCertificate

# set view engine for web pages
Set-PodeViewEngine -Type Pode

# setup client cert auth
New-PodeAuthScheme -ClientCertificate | Add-PodeAuth -Name 'Validate' -Sessionless -ScriptBlock {
param($cert, $errors)

# validate the thumbprint - here you would check a real cert store, or database
if ($cert.Thumbprint -ieq '2561B2BD3CF292FF55F72692FB252E6B3D9879C2') {
return @{
User = @{
ID ='M0R7Y302'
Name = 'Morty'
Type = 'Human'
}
}
}

# an invalid cert
return @{ Message = 'Invalid certificate supplied' }
}

# GET request for web page at "/"
Add-PodeRoute -Method Get -Path '/' -Authentication 'Validate' -ScriptBlock {
param($e)
#$e.Request.ClientCertificate | out-default
Write-PodeViewResponse -Path 'simple' -Data @{ 'numbers' = @(1, 2, 3); }
}

# GET request throws fake "500" server error status code
Add-PodeRoute -Method Get -Path '/error' -Authentication 'Validate' -ScriptBlock {
param($e)
Set-PodeResponseStatus -Code 500
}

}
4 changes: 2 additions & 2 deletions examples/web-pages-https.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ Start-PodeServer {

# GET request for web page at "/"
Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
param($session)
param($e)
Write-PodeViewResponse -Path 'simple' -Data @{ 'numbers' = @(1, 2, 3); }
}

# GET request throws fake "500" server error status code
Add-PodeRoute -Method Get -Path '/error' -ScriptBlock {
param($session)
param($e)
Set-PodeResponseStatus -Code 500
}

Expand Down
2 changes: 1 addition & 1 deletion src/Listener/PodeContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private void NewRequest()
// attempt to open the request stream
try
{
Request.Open(PodeSocket.Certificate, PodeSocket.Protocols);
Request.Open(PodeSocket.Certificate, PodeSocket.Protocols, PodeSocket.AllowClientCertificate);
State = PodeContextState.Open;
}
catch
Expand Down
17 changes: 10 additions & 7 deletions src/Listener/PodeRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public class PodeRequest : IDisposable
public virtual bool CloseImmediately { get => false; }

public Stream InputStream { get; private set; }
public X509Certificate2 ClientCertificate { get; private set; }
public SslPolicyErrors ClientCertificateErrors { get; private set; }
public HttpRequestException Error { get; private set; }

private Socket Socket;
Expand All @@ -42,7 +44,7 @@ public PodeRequest(PodeRequest request)
Context = request.Context;
}

public void Open(X509Certificate certificate, SslProtocols protocols)
public void Open(X509Certificate certificate, SslProtocols protocols, bool allowClientCertificate)
{
// ssl or not?
IsSsl = (certificate != default(X509Certificate));
Expand All @@ -57,18 +59,19 @@ public void Open(X509Certificate certificate, SslProtocols protocols)

// otherwise, convert the stream to an ssl stream
var ssl = new SslStream(InputStream, false, new RemoteCertificateValidationCallback(ValidateCertificateCallback));
ssl.AuthenticateAsServerAsync(certificate, false, protocols, false).Wait(Context.Listener.CancellationToken);
ssl.AuthenticateAsServerAsync(certificate, allowClientCertificate, protocols, false).Wait(Context.Listener.CancellationToken);
InputStream = ssl;
}

private bool ValidateCertificateCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (certificate == default(X509Certificate))
{
return true;
}
ClientCertificateErrors = sslPolicyErrors;

ClientCertificate = certificate == default(X509Certificate)
? default(X509Certificate2)
: new X509Certificate2(certificate);

return (sslPolicyErrors != SslPolicyErrors.None);
return true;
}

public void Receive()
Expand Down
4 changes: 3 additions & 1 deletion src/Listener/PodeSocket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class PodeSocket : IDisposable
public int Port { get; private set; }
public IPEndPoint Endpoint { get; private set; }
public X509Certificate Certificate { get; private set; }
public bool AllowClientCertificate { get; private set; }
public SslProtocols Protocols { get; private set; }
public Socket Socket { get; private set; }

Expand All @@ -35,11 +36,12 @@ public int ReceiveTimeout
set => Socket.ReceiveTimeout = value;
}

public PodeSocket(IPAddress ipAddress, int port, SslProtocols protocols, X509Certificate certificate = null)
public PodeSocket(IPAddress ipAddress, int port, SslProtocols protocols, X509Certificate certificate = null, bool allowClientCertificate = false)
{
IPAddress = ipAddress;
Port = port;
Certificate = certificate;
AllowClientCertificate = allowClientCertificate;
Protocols = protocols;
Endpoint = new IPEndPoint(ipAddress, port);

Expand Down
36 changes: 36 additions & 0 deletions src/Private/Authentication.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,42 @@ function Get-PodeAuthBasicType
}
}

function Get-PodeAuthClientCertificateType
{
return {
param($e, $options)
$cert = $e.Request.ClientCertificate

# ensure we have a client cert
if ($null -eq $cert) {
return @{
Message = 'No client certificate supplied'
Code = 401
}
}

# ensure the cert has a thumbprint
if ([string]::IsNullOrWhiteSpace($cert.Thumbprint)) {
return @{
Message = 'Invalid client certificate supplied'
Code = 401
}
}

# ensure the cert hasn't expired, or has it even started
$now = [datetime]::Now
if (($cert.NotAfter -lt $now) -or ($cert.NotBefore -gt $now)) {
return @{
Message = 'Invalid client certificate supplied'
Code = 401
}
}

# return data for calling validator
return @($cert, $e.Request.ClientCertificateErrors)
}
}

function Get-PodeAuthBearerType
{
return {
Expand Down
3 changes: 2 additions & 1 deletion src/Private/PodeServer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ function Start-PodeWebServer
Address = $_ip
Port = $_.Port
Certificate = $_.Certificate.Raw
AllowClientCertificate = $_.Certificate.AllowClientCertificate
HostName = $_.Url
}
}
Expand All @@ -45,7 +46,7 @@ function Start-PodeWebServer
{
# register endpoints on the listener
$endpoints | ForEach-Object {
$socket = [PodeSocket]::new($_.Address, $_.Port, $PodeContext.Server.Sockets.Ssl.Protocols, $_.Certificate)
$socket = [PodeSocket]::new($_.Address, $_.Port, $PodeContext.Server.Sockets.Ssl.Protocols, $_.Certificate, $_.AllowClientCertificate)
$socket.ReceiveTimeout = $PodeContext.Server.Sockets.ReceiveTimeout
$listener.Add($socket)
}
Expand Down
3 changes: 2 additions & 1 deletion src/Private/SignalServer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function Start-PodeSignalServer
Address = $_ip
Port = $_.Port
Certificate = $_.Certificate.Raw
AllowClientCertificate = $_.Certificate.AllowClientCertificate
HostName = $_.Url
}
}
Expand All @@ -27,7 +28,7 @@ function Start-PodeSignalServer
{
# register endpoints on the listener
$endpoints | ForEach-Object {
$socket = [PodeSocket]::new($_.Address, $_.Port, $PodeContext.Server.Sockets.Ssl.Protocols, $_.Certificate)
$socket = [PodeSocket]::new($_.Address, $_.Port, $PodeContext.Server.Sockets.Ssl.Protocols, $_.Certificate, $_.AllowClientCertificate)
$socket.ReceiveTimeout = $PodeContext.Server.Sockets.ReceiveTimeout
$listener.Add($socket)
}
Expand Down
21 changes: 21 additions & 0 deletions src/Public/Authentication.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ If supplied, will use the inbuilt Digest Authentication credentials retriever.
.PARAMETER Bearer
If supplied, will use the inbuilt Bearer Authentication token retriever.
.PARAMETER ClientCertificate
If supplied, will use the inbuilt Client Certificate Authentication validation.
.PARAMETER Scope
An optional array of Scopes for Bearer Authentication. (These are case-sensitive)
Expand Down Expand Up @@ -135,6 +138,10 @@ function New-PodeAuthScheme
[switch]
$Bearer,

[Parameter(ParameterSetName='ClientCertificate')]
[switch]
$ClientCertificate,

[Parameter(ParameterSetName='Bearer')]
[string[]]
$Scope
Expand Down Expand Up @@ -162,6 +169,20 @@ function New-PodeAuthScheme
}
}

'clientcertificate' {
return @{
Name = 'Mutual'
Realm = (Protect-PodeValue -Value $Realm -Default $_realm)
ScriptBlock = @{
Script = (Get-PodeAuthClientCertificateType)
UsingVariables = $null
}
PostValidator = $null
Scheme = 'http'
Arguments = @{}
}
}

'digest' {
return @{
Name = 'Digest'
Expand Down
9 changes: 8 additions & 1 deletion src/Public/Core.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,9 @@ Ignore Adminstrator checks for non-localhost endpoints.
.PARAMETER SelfSigned
Create and bind a self-signed certifcate for HTTPS endpoints.
.PARAMETER AllowClientCertificate
Allow for client certificates to be sent on requests.
.EXAMPLE
Add-PodeEndpoint -Address localhost -Port 8090 -Protocol Http
Expand Down Expand Up @@ -718,7 +721,10 @@ function Add-PodeEndpoint

[Parameter(ParameterSetName='CertSelf')]
[switch]
$SelfSigned
$SelfSigned,

[switch]
$AllowClientCertificate
)

# error if serverless
Expand Down Expand Up @@ -771,6 +777,7 @@ function Add-PodeEndpoint
Certificate = @{
Raw = $X509Certificate
SelfSigned = $SelfSigned
AllowClientCertificate = $AllowClientCertificate
}
}

Expand Down

0 comments on commit be0484a

Please sign in to comment.