Skip to content

Commit

Permalink
Add support for PowerShell scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
Oren Novotny committed Sep 13, 2016
1 parent 1eee588 commit 889b77f
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/SignClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Refit;
using SignServiceClient;

namespace SignClient
{
Expand Down
2 changes: 1 addition & 1 deletion src/SignClient/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"TenantId": "71048637-3782-41a3-b6b2-6f4ac8a25ae0"
},
"Service": {
"Url": "https://oren-sign-service.azurewebsites.net/",
"Url": "https://localhost:44351/",
"ResourceId": "https://novotny.org/SignService"
}
}
Expand Down
11 changes: 5 additions & 6 deletions src/SignService/Controllers/SignController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using SignService.SigningTools;

namespace SignService.Controllers
{
Expand All @@ -18,12 +19,12 @@ namespace SignService.Controllers
[Route("[controller]")]
public class SignController : Controller
{
readonly ICodeSignService codeSignService;
readonly ISigningToolAggregate codeSignService;
readonly ILogger<SignController> logger;

readonly string ziptoolPath;

public SignController(IHostingEnvironment environment, ICodeSignService codeSignService, ILogger<SignController> logger)
public SignController(IHostingEnvironment environment, ISigningToolAggregate codeSignService, ILogger<SignController> logger)
{
this.codeSignService = codeSignService;
this.logger = logger;
Expand Down Expand Up @@ -52,7 +53,7 @@ public async Task<IActionResult> SignSingleFile(IFormFile source, string name, s
// Do work and then load the file into memory so we can delete it before the response is complete
var fi = new FileInfo(fileName);

codeSignService.Submit(name, description, descriptionUrl, new[] {fileName});
await codeSignService.Submit(name, description, descriptionUrl, new[] {fileName});


byte[] buffer;
Expand Down Expand Up @@ -105,13 +106,11 @@ public async Task<IActionResult> SignZipFile(IFormFile source, string name, stri
ZipFile.ExtractToDirectory(inputFileName, outputDir);

var filesToSign = Directory.EnumerateFiles(outputDir, "*.*", SearchOption.AllDirectories)
.Where(f => f.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) ||
f.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
.ToList();


// This will block until it's done
codeSignService.Submit(name, description, descriptionUrl, filesToSign);
await codeSignService.Submit(name, description, descriptionUrl, filesToSign);


// They were signed in-place, now zip them back up
Expand Down
84 changes: 84 additions & 0 deletions src/SignService/SigningTools/PowerShellCodeSignService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace SignService
{


class PowerShellCodeSignService : ICodeSignService
{
readonly string timeStampUrl;
readonly string thumbprint;
readonly ILogger<PowerShellCodeSignService> logger;


// Four things at once as we're hitting the sign server
readonly ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 4 };


public PowerShellCodeSignService(string timeStampUrl, string thumbprint,ILogger<PowerShellCodeSignService> logger)
{
this.timeStampUrl = timeStampUrl;
this.thumbprint = thumbprint;
this.logger = logger;
}

public Task Submit(string name, string description, string descriptionUrl, IList<string> files)
{
// Explicitly put this on a thread because Parallel.ForEach blocks
return Task.Run(() => SubmitInternal(name, description, descriptionUrl, files));
}

void SubmitInternal(string name, string description, string descriptionUrl, IList<string> files)
{
logger.LogInformation("Signing PowerShell job {0} with {1} files", name, files.Count());



Parallel.ForEach(files, options, (file, state) =>
{
// Sign it
var signtool = new Process
{
StartInfo =
{
FileName = "powershell.exe",
UseShellExecute = false,
RedirectStandardError = false,
RedirectStandardOutput = false,
Arguments = $@"-Command ""Set-AuthenticodeSignature {file} @(Get-ChildItem -recurse cert: | where {{$_.Thumbprint -eq '{thumbprint}'}})[0] -TimestampServer '{timeStampUrl}'"""
}
};
logger.LogInformation(@"""{0}"" {1}", signtool.StartInfo.FileName, signtool.StartInfo.Arguments);
signtool.Start();
if (!signtool.WaitForExit(30 * 1000))
{
signtool.Kill();
logger.LogError("Error: Set-AuthenticodeSignature took too long to respond {0}", signtool.ExitCode);
throw new Exception($"Set-AuthenticodeSignature took too long to respond with {signtool.StartInfo.Arguments}");
}
if (signtool.ExitCode != 0)
{
logger.LogError("Error: Set-AuthenticodeSignaturel returned {0}", signtool.ExitCode);
throw new Exception($"Set-AuthenticodeSignature returned error with {signtool.StartInfo.Arguments}");
}
signtool.Dispose();

});
}

public IReadOnlyCollection<string> SupportedFileExtensions { get; } = new List<string>
{
".ps1",
".psm1"
};
public bool IsDefault => false;
}
}
78 changes: 78 additions & 0 deletions src/SignService/SigningTools/SigningToolAggregate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;

namespace SignService.SigningTools
{
public interface ISigningToolAggregate
{
Task Submit(string name, string description, string descriptionUrl, IList<string> files);
}

public class SigningToolAggregate : ISigningToolAggregate
{
readonly ICodeSignService defaultCodeSignService;
readonly IDictionary<string, ICodeSignService> codeSignServices;


public SigningToolAggregate(IList<ICodeSignService> services)
{
// pe files
defaultCodeSignService = services.Single(c => c.IsDefault);

var list = from cs in services
from ext in cs.SupportedFileExtensions
where !cs.IsDefault
select new { cs, ext };

this.codeSignServices = list.ToDictionary(k => k.ext.ToLowerInvariant(), v => v.cs);
}



public async Task Submit(string name, string description, string descriptionUrl, IList<string> files)
{
// split by code sign service and fallback to default

var grouped = (from kvp in codeSignServices
join file in files on kvp.Key equals Path.GetExtension(file).ToLowerInvariant()
group file by kvp.Value into g
select g).ToList();

// get all files and exclude existing; create default group
var defaultFiles = files.Except(grouped.SelectMany(g => g))
.Where(IsPeFile)
.Select(f => new {defaultCodeSignService,f })
.GroupBy(a => a.defaultCodeSignService, k => k.f)
.SingleOrDefault(); // one group here

if(defaultFiles != null)
grouped.Add(defaultFiles);


await Task.WhenAll(grouped.Select(g => g.Key.Submit(name, description, descriptionUrl, g.ToList())));
}

static bool IsPeFile(string file)
{
using (var str = File.OpenRead(file))
{
var buffer = new byte[2];
if (str.CanRead)
{
var read = str.Read(buffer, 0, 2);
if (read == 2)
{
// Look for the magic MZ header
return (buffer[0] == 0x4d && buffer[1] == 0x5a);
}
}
}

return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ namespace SignService
{
public interface ICodeSignService
{
void Submit(string name, string description, string descriptionUrl, IList<string> files);
Task Submit(string name, string description, string descriptionUrl, IList<string> files);

IReadOnlyCollection<string> SupportedFileExtensions { get; }

bool IsDefault { get; }
}

class SigntoolCodeSignService : ICodeSignService
Expand All @@ -35,9 +39,15 @@ public SigntoolCodeSignService(string timeStampUrl, string thumbprint, string co
signtoolPath = Path.Combine(contentPath, "tools\\signtool.exe");
}

public void Submit(string name, string description, string descriptionUrl, IList<string> files)
public Task Submit(string name, string description, string descriptionUrl, IList<string> files)
{
logger.LogInformation("Signing job {0} with {1} files", name, files.Count());
// Explicitly put this on a thread because Parallel.ForEach blocks
return Task.Run(() => SubmitInternal(name, description, descriptionUrl, files));
}

void SubmitInternal(string name, string description, string descriptionUrl, IList<string> files)
{
logger.LogInformation("Signing SignTool job {0} with {1} files", name, files.Count());

var descArgsList = new List<string>();
if (!string.IsNullOrWhiteSpace(description))
Expand All @@ -57,7 +67,7 @@ public void Submit(string name, string description, string descriptionUrl, IList
var descArgs = string.Join(" ", descArgsList);


Parallel.ForEach(files, options, (file, state) =>
{
// Sign it with sha1
Expand Down Expand Up @@ -115,5 +125,8 @@ public void Submit(string name, string description, string descriptionUrl, IList
signtool.Dispose();
});
}

public IReadOnlyCollection<string> SupportedFileExtensions { get; } = new List<string>();
public bool IsDefault => true;
}
}
9 changes: 9 additions & 0 deletions src/SignService/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SignService.SigningTools;

namespace SignService
{
Expand Down Expand Up @@ -49,6 +50,14 @@ public void ConfigureServices(IServiceCollection services)
sp.GetService<ILogger<SigntoolCodeSignService>>());
});

services.AddSingleton<ICodeSignService>(sp => new PowerShellCodeSignService(
Configuration["CertificateInfo:TimeStampUrl"],
Configuration["CertificateInfo:Thumbprint"],
sp.GetService<ILogger<PowerShellCodeSignService>>()));


services.AddSingleton<ISigningToolAggregate, SigningToolAggregate>(sp => new SigningToolAggregate(sp.GetServices<ICodeSignService>().ToList()));

services.AddMvc();
}

Expand Down

0 comments on commit 889b77f

Please sign in to comment.