Skip to content

Commit dfd5541

Browse files
committed
fix signature check
1 parent 478782e commit dfd5541

File tree

3 files changed

+138
-79
lines changed

3 files changed

+138
-79
lines changed
Lines changed: 0 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11

22
using Microsoft.AspNetCore.Authentication;
33
using Microsoft.Extensions.Options;
4-
using rubberduckvba.Server.Api.Admin;
54
using rubberduckvba.Server.Services;
65
using System.Security.Claims;
7-
using System.Security.Cryptography;
8-
using System.Text;
96
using System.Text.Encodings.Web;
107

118
namespace rubberduckvba.Server;
@@ -35,79 +32,3 @@ protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
3532
: AuthenticateResult.NoResult();
3633
}
3734
}
38-
39-
public class WebhookAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
40-
{
41-
private readonly ConfigurationOptions _configuration;
42-
43-
public WebhookAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder,
44-
ConfigurationOptions configuration)
45-
: base(options, logger, encoder)
46-
{
47-
_configuration = configuration;
48-
}
49-
50-
protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
51-
{
52-
return await Task.Run(() =>
53-
{
54-
var xGitHubEvent = Context.Request.Headers["X-GitHub-Event"];
55-
var xGitHubDelivery = Context.Request.Headers["X-GitHub-Delivery"];
56-
var xHubSignature = Context.Request.Headers["X-Hub-Signature"];
57-
var xHubSignature256 = Context.Request.Headers["X-Hub-Signature-256"];
58-
59-
if (!xGitHubEvent.Contains("push"))
60-
{
61-
// only authenticate push events
62-
return AuthenticateResult.NoResult();
63-
}
64-
65-
if (!Guid.TryParse(xGitHubDelivery.SingleOrDefault(), out _))
66-
{
67-
// delivery should parse as a GUID
68-
return AuthenticateResult.NoResult();
69-
}
70-
71-
if (!xHubSignature.Any())
72-
{
73-
// signature header should be present
74-
return AuthenticateResult.NoResult();
75-
}
76-
77-
var signature = xHubSignature256.SingleOrDefault();
78-
79-
using var reader = new StreamReader(Context.Request.Body);
80-
var payload = reader.ReadToEndAsync().GetAwaiter().GetResult();
81-
82-
if (!IsValidSignature(signature, payload))
83-
{
84-
// encrypted signature must be present
85-
return AuthenticateResult.NoResult();
86-
}
87-
88-
var identity = new ClaimsIdentity("webhook", ClaimTypes.Name, ClaimTypes.Role);
89-
identity.AddClaim(new Claim(ClaimTypes.Name, "rubberduck-vba-releasebot"));
90-
identity.AddClaim(new Claim(ClaimTypes.Role, "rubberduck-webhook"));
91-
identity.AddClaim(new Claim(ClaimTypes.Authentication, "webhook-signature"));
92-
93-
var principal = new ClaimsPrincipal(identity);
94-
return AuthenticateResult.Success(new AuthenticationTicket(principal, "webhook-signature"));
95-
});
96-
}
97-
98-
private bool IsValidSignature(string? signature, string payload)
99-
{
100-
if (string.IsNullOrWhiteSpace(signature))
101-
{
102-
return false;
103-
}
104-
105-
using var sha256 = SHA256.Create();
106-
107-
var secret = _configuration.GitHubOptions.Value.WebhookToken;
108-
var bytes = Encoding.UTF8.GetBytes(secret + payload);
109-
var check = $"sha256={Encoding.UTF8.GetString(sha256.ComputeHash(bytes))}";
110-
111-
return check == payload;
112-
}
113-
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+

2+
using Microsoft.AspNetCore.Authentication;
3+
using Microsoft.Extensions.Options;
4+
using System.Security.Claims;
5+
using System.Text.Encodings.Web;
6+
7+
namespace rubberduckvba.Server;
8+
9+
public class WebhookAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
10+
{
11+
private readonly WebhookSignatureValidationService _service;
12+
13+
public WebhookAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder,
14+
WebhookSignatureValidationService service)
15+
: base(options, logger, encoder)
16+
{
17+
_service = service;
18+
}
19+
20+
protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
21+
{
22+
return await Task.Run(() =>
23+
{
24+
var userAgent = Context.Request.Headers.UserAgent;
25+
var xGitHubEvent = Context.Request.Headers["X-GitHub-Event"].OfType<string>().ToArray();
26+
var xGitHubDelivery = Context.Request.Headers["X-GitHub-Delivery"].OfType<string>().ToArray();
27+
var xHubSignature = Context.Request.Headers["X-Hub-Signature"].OfType<string>().ToArray();
28+
var xHubSignature256 = Context.Request.Headers["X-Hub-Signature-256"].OfType<string>().ToArray();
29+
30+
using var reader = new StreamReader(Context.Request.Body);
31+
var payload = reader.ReadToEndAsync().GetAwaiter().GetResult();
32+
33+
if (_service.Validate(payload, userAgent, xGitHubEvent, xGitHubDelivery, xHubSignature, xHubSignature256))
34+
{
35+
var principal = CreatePrincipal();
36+
var ticket = new AuthenticationTicket(principal, "webhook-signature");
37+
38+
return AuthenticateResult.Success(ticket);
39+
}
40+
41+
return AuthenticateResult.NoResult();
42+
});
43+
}
44+
45+
private static ClaimsPrincipal CreatePrincipal()
46+
{
47+
var identity = new ClaimsIdentity("webhook", ClaimTypes.Name, ClaimTypes.Role);
48+
49+
identity.AddClaim(new Claim(ClaimTypes.Name, "rubberduck-vba-releasebot"));
50+
identity.AddClaim(new Claim(ClaimTypes.Role, "rubberduck-webhook"));
51+
identity.AddClaim(new Claim(ClaimTypes.Authentication, "webhook-signature"));
52+
53+
var principal = new ClaimsPrincipal(identity);
54+
return principal;
55+
}
56+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using rubberduckvba.Server.Api.Admin;
2+
using System.Diagnostics;
3+
using System.Security.Cryptography;
4+
using System.Text;
5+
6+
namespace rubberduckvba.Server;
7+
8+
public class WebhookSignatureValidationService(ConfigurationOptions configuration)
9+
{
10+
public bool Validate(
11+
string payload,
12+
string? userAgent,
13+
string[] xGitHubEvent,
14+
string[] xGitHubDelivery,
15+
string[] xHubSignature,
16+
string[] xHubSignature256
17+
)
18+
{
19+
if (!(userAgent ?? string.Empty).StartsWith("GitHub-Hookshot/"))
20+
{
21+
// user agent must be GitHub hookshot
22+
LogMissingHeader("USER-AGENT");
23+
return false;
24+
}
25+
26+
if (!xGitHubEvent.Contains("push"))
27+
{
28+
// only authenticate push events
29+
LogMissingHeader("X-GITHUB-EVENT");
30+
return false;
31+
}
32+
33+
if (!Guid.TryParse(xGitHubDelivery.SingleOrDefault(), out _))
34+
{
35+
// delivery should parse as a GUID
36+
LogMissingHeader("X-GITHUB-DELIVERY");
37+
return false;
38+
}
39+
40+
if (!xHubSignature.Any())
41+
{
42+
// SHA-1 signature header must be present
43+
LogMissingHeader("X-HUB-SIGNATURE");
44+
return false;
45+
}
46+
47+
var signature = xHubSignature256.SingleOrDefault();
48+
if (signature == default)
49+
{
50+
// SHA-256 signature header must be present
51+
LogMissingHeader("X-HUB-SIGNATURE-256");
52+
return false;
53+
}
54+
55+
if (!IsValidSignature(signature, payload))
56+
{
57+
// SHA-256 signature must match
58+
Debug.WriteLine("Signature validation failed");
59+
return false;
60+
}
61+
62+
return true;
63+
}
64+
65+
//[Conditional("DEBUG")]
66+
private void LogMissingHeader(string header) => Console.WriteLine($"** Webhook validation failed. Missing header: [{header}]");
67+
68+
private bool IsValidSignature(string? signature, string payload)
69+
{
70+
if (string.IsNullOrWhiteSpace(signature))
71+
{
72+
return false;
73+
}
74+
using var sha256 = SHA256.Create();
75+
76+
var secret = configuration.GitHubOptions.Value.WebhookToken;
77+
var bytes = Encoding.UTF8.GetBytes(secret + payload);
78+
var check = $"sha256={Encoding.UTF8.GetString(sha256.ComputeHash(bytes))}";
79+
80+
return signature == check;
81+
}
82+
}

0 commit comments

Comments
 (0)