├── .gitignore ├── LICENSE ├── README.md └── src ├── SafenetSign ├── CertificateStore.cs ├── CodeSigner.cs ├── CommandParameters.cs ├── Common.props ├── Logger.cs ├── Native │ ├── APPX_SIP_CLIENT_DATA.cs │ ├── CRYPTOAPI_BLOB.cs │ ├── Constants.cs │ ├── LoadLibraryFlags.cs │ ├── SIGNER_CERT.cs │ ├── SIGNER_CERT_STORE_INFO.cs │ ├── SIGNER_FILE_INFO.cs │ ├── SIGNER_PROVIDER_INFO.cs │ ├── SIGNER_SIGNATURE_INFO.cs │ ├── SIGNER_SIGN_EX2_PARAMS.cs │ └── SIGNER_SUBJECT_INFO.cs ├── NativeMethods.cs ├── Program.cs ├── SafenetSign.csproj ├── SafenetSignNoTool.csproj ├── SignMode.cs ├── SigningException.cs └── releasenotes.md ├── Signer.sln └── Signer ├── Main.cpp ├── Signer.h ├── Signer.vcxproj └── Signer.vcxproj.filters /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.pch 68 | *.pdb 69 | *.pgc 70 | *.pgd 71 | *.rsp 72 | *.sbr 73 | *.tlb 74 | *.tli 75 | *.tlh 76 | *.tmp 77 | *.tmp_proj 78 | *.log 79 | *.vspscc 80 | *.vssscc 81 | .builds 82 | *.pidb 83 | *.svclog 84 | *.scc 85 | 86 | # Chutzpah Test files 87 | _Chutzpah* 88 | 89 | # Visual C++ cache files 90 | ipch/ 91 | *.aps 92 | *.ncb 93 | *.opendb 94 | *.opensdf 95 | *.sdf 96 | *.cachefile 97 | *.VC.db 98 | *.VC.VC.opendb 99 | 100 | # Visual Studio profiler 101 | *.psess 102 | *.vsp 103 | *.vspx 104 | *.sap 105 | 106 | # Visual Studio Trace Files 107 | *.e2e 108 | 109 | # TFS 2012 Local Workspace 110 | $tf/ 111 | 112 | # Guidance Automation Toolkit 113 | *.gpState 114 | 115 | # ReSharper is a .NET coding add-in 116 | _ReSharper*/ 117 | *.[Rr]e[Ss]harper 118 | *.DotSettings.user 119 | 120 | # JustCode is a .NET coding add-in 121 | .JustCode 122 | 123 | # TeamCity is a build add-in 124 | _TeamCity* 125 | 126 | # DotCover is a Code Coverage Tool 127 | *.dotCover 128 | 129 | # AxoCover is a Code Coverage Tool 130 | .axoCover/* 131 | !.axoCover/settings.json 132 | 133 | # Visual Studio code coverage results 134 | *.coverage 135 | *.coveragexml 136 | 137 | # NCrunch 138 | _NCrunch_* 139 | .*crunch*.local.xml 140 | nCrunchTemp_* 141 | 142 | # MightyMoose 143 | *.mm.* 144 | AutoTest.Net/ 145 | 146 | # Web workbench (sass) 147 | .sass-cache/ 148 | 149 | # Installshield output folder 150 | [Ee]xpress/ 151 | 152 | # DocProject is a documentation generator add-in 153 | DocProject/buildhelp/ 154 | DocProject/Help/*.HxT 155 | DocProject/Help/*.HxC 156 | DocProject/Help/*.hhc 157 | DocProject/Help/*.hhk 158 | DocProject/Help/*.hhp 159 | DocProject/Help/Html2 160 | DocProject/Help/html 161 | 162 | # Click-Once directory 163 | publish/ 164 | 165 | # Publish Web Output 166 | *.[Pp]ublish.xml 167 | *.azurePubxml 168 | # Note: Comment the next line if you want to checkin your web deploy settings, 169 | # but database connection strings (with potential passwords) will be unencrypted 170 | *.pubxml 171 | *.publishproj 172 | 173 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 174 | # checkin your Azure Web App publish settings, but sensitive information contained 175 | # in these scripts will be unencrypted 176 | PublishScripts/ 177 | 178 | # NuGet Packages 179 | *.nupkg 180 | # The packages folder can be ignored because of Package Restore 181 | **/[Pp]ackages/* 182 | # except build/, which is used as an MSBuild target. 183 | !**/[Pp]ackages/build/ 184 | # Uncomment if necessary however generally it will be regenerated when needed 185 | #!**/[Pp]ackages/repositories.config 186 | # NuGet v3's project.json files produces more ignorable files 187 | *.nuget.props 188 | *.nuget.targets 189 | 190 | # Microsoft Azure Build Output 191 | csx/ 192 | *.build.csdef 193 | 194 | # Microsoft Azure Emulator 195 | ecf/ 196 | rcf/ 197 | 198 | # Windows Store app package directories and files 199 | AppPackages/ 200 | BundleArtifacts/ 201 | Package.StoreAssociation.xml 202 | _pkginfo.txt 203 | *.appx 204 | 205 | # Visual Studio cache files 206 | # files ending in .cache can be ignored 207 | *.[Cc]ache 208 | # but keep track of directories ending in .cache 209 | !*.[Cc]ache/ 210 | 211 | # Others 212 | ClientBin/ 213 | ~$* 214 | *~ 215 | *.dbmdl 216 | *.dbproj.schemaview 217 | *.jfm 218 | *.pfx 219 | *.publishsettings 220 | orleans.codegen.cs 221 | 222 | # Including strong name files can present a security risk 223 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 224 | #*.snk 225 | 226 | # Since there are multiple workflows, uncomment next line to ignore bower_components 227 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 228 | #bower_components/ 229 | 230 | # RIA/Silverlight projects 231 | Generated_Code/ 232 | 233 | # Backup & report files from converting an old project file 234 | # to a newer Visual Studio version. Backup files are not needed, 235 | # because we have git ;-) 236 | _UpgradeReport_Files/ 237 | Backup*/ 238 | UpgradeLog*.XML 239 | UpgradeLog*.htm 240 | ServiceFabricBackup/ 241 | 242 | # SQL Server files 243 | *.mdf 244 | *.ldf 245 | *.ndf 246 | 247 | # Business Intelligence projects 248 | *.rdl.data 249 | *.bim.layout 250 | *.bim_*.settings 251 | 252 | # Microsoft Fakes 253 | FakesAssemblies/ 254 | 255 | # GhostDoc plugin setting file 256 | *.GhostDoc.xml 257 | 258 | # Node.js Tools for Visual Studio 259 | .ntvs_analysis.dat 260 | node_modules/ 261 | 262 | # Visual Studio 6 build log 263 | *.plg 264 | 265 | # Visual Studio 6 workspace options file 266 | *.opt 267 | 268 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 269 | *.vbw 270 | 271 | # Visual Studio LightSwitch build output 272 | **/*.HTMLClient/GeneratedArtifacts 273 | **/*.DesktopClient/GeneratedArtifacts 274 | **/*.DesktopClient/ModelManifest.xml 275 | **/*.Server/GeneratedArtifacts 276 | **/*.Server/ModelManifest.xml 277 | _Pvt_Extensions 278 | 279 | # Paket dependency manager 280 | .paket/paket.exe 281 | paket-files/ 282 | 283 | # FAKE - F# Make 284 | .fake/ 285 | 286 | # JetBrains Rider 287 | .idea/ 288 | *.sln.iml 289 | 290 | # CodeRush 291 | .cr/ 292 | 293 | # Python Tools for Visual Studio (PTVS) 294 | __pycache__/ 295 | *.pyc 296 | 297 | # Cake - Uncomment if you are using it 298 | # tools/** 299 | # !tools/packages.config 300 | 301 | # Tabs Studio 302 | *.tss 303 | 304 | # Telerik's JustMock configuration file 305 | *.jmconfig 306 | 307 | # BizTalk build output 308 | *.btp.cs 309 | *.btm.cs 310 | *.odx.cs 311 | *.xsd.cs 312 | 313 | # OpenCover UI analysis results 314 | OpenCover/ 315 | 316 | # Azure Stream Analytics local run output 317 | ASALocalRun/ 318 | 319 | # MSBuild Binary and Structured Log 320 | *.binlog 321 | 322 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Marek Linka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Update March 16, 2020 2 | 3 | This project is no longer maintained. Instead, you can use a sign-tool compatible approach detailed in [this issue](https://github.com/mareklinka/SafeNetTokenSigner/issues/8). 4 | 5 | ---- 6 | 7 | [![Build status](https://dev.azure.com/mar3ek/safenet-signer/_apis/build/status/safenet-signer-CI)](https://dev.azure.com/mar3ek/safenet-signer/_build/latest?definitionId=13) 8 | 9 | # SafeNetTokenSigner 10 | A SignTool alternative that can unlock SafeNet hardware tokens without user interaction. 11 | 12 | ## What it does 13 | Signing your released code is an important step in ensuring integrity of the code and trust of your users. Unfortunately, some code signing certificates come to you on hardware tokens protected by a PIN and so it's very difficult (or impossible) to use them in any kind of automated manner (such as CI pipelines). 14 | 15 | This tools attempts to solve this particular problem by allowing you to code-sign your binaries without user interaction even when the certificate requires a PIN to be unlocked. All this is done with standard Windows APIs and distributed as a dotnet global tool so it should work as long as you have the dotnet CLI installed. 16 | 17 | ## How it works 18 | [Details available in Wiki](https://github.com/mareklinka/SafeNetTokenSigner/wiki/History-of-the-project-and-implementation-details) 19 | 20 | ## Usage 21 | To make use of this tool, you either need to install it as a dotnet tool or compile it from source yourself. This tool is __x64 WINDOWS ONLY__ as it uses several Win32 API functions to delegate the actual signing to the OS. 22 | 23 | ### Installing the tool via [NuGet](https://www.nuget.org/packages/SafenetSign) 24 | On the machine where you'll be doing your code signing: 25 | 1. Make sure you have .NET Core runtime installed on the machine (you can get the installer [here](https://dotnet.microsoft.com/download)) 26 | 2. Run `dotnet tool install --global SafenetSign --version [specific version]` 27 | * The version parameter is currently required as the tool is marked as pre-release 28 | * Get the latest version number on [NuGet.org](https://www.nuget.org/packages/SafenetSign) 29 | * You don't necessarilly need to install with `--global` but then the tools won't be available machine wide 30 | * If you are unfamiliar with dotnet tools, you can read the [docs](https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools) 31 | 3. Run `safenet-sign` 32 | * If you installed without `--global`, you will need to navigate to your installation path first 33 | * The tool should display a command line parameter help, confirming that it works 34 | * You *might* need to restart your command line for the changes to take effect 35 | 36 | ### Compiling from source 37 | 1. You'll need [.NET Core 2.2 SDK](https://dotnet.microsoft.com/download) 38 | 2. Clone the repository 39 | 3. Navigate to `src\SafenetSign` 40 | 4. Run `dotnet publish .\SafenetSignNoTool.csproj -c Release --self-contained false` 41 | * or you can use Visual Studio or VS Code to open the solution and look around 42 | 5. Put the output (in `src\SafenetSign\bin\Release\netcoreapp2.2\win-x64\publish`) wherever you need it 43 | 6. Run `safenetsign.exe` to get the command line parameter help 44 | 45 | ### Comand line options 46 | The tool has several required positional arguments and two optional arguments. You can get the full help by running the tool without any parameters (`safenet-sign`). 47 | 48 | Required parameters: 49 | 50 | * the signing certificate thumbprint (SHA1, e.g. `8cfaa82e5c3842ffee22d82d8ff812b3a1de1ebb`) 51 | * private key container name - for SafeNet tokens, you can get this by looking into the SafeNet app (see this [SO question](https://stackoverflow.com/a/47894907/1453109)) 52 | * the target store that contains the certificate (valid values are `user` and `machine`) 53 | * the PIN used to protect the token's private keys 54 | * timestamp URL (the code will perform timestamping automatically, e.g. `http://timestamp.verisign.com/scripts/timstamp.dll`) 55 | * mode is either `pe` or `appx`, depending on what file you are trying to sign (`pe` for normal EXE/DLL etc., `appx` for UWP app bundles) 56 | * the path to the file you want to sign (can be relative) 57 | 58 | Optional parameters: 59 | 60 | * `-v` - switch, enables verbose activity logging for debugging or when you want to report an issue with the tool 61 | * `-m [path]` - the tools makes use of Win32 APIs located in `MSSign32.dll`. This option allows you to specify a path to the library (useful for non-standard installations and similar but should otherwise not be required) 62 | 63 | Schema: 64 | 65 | ` [-v] [-m path]` 66 | 67 | Examples: 68 | 69 | .NET Core - dotnet tool 70 | 71 | `safenet-sign 8cfaa82e5c3842ffee22d82d8ff812b3a1de1ebb my-container-name 12345678 user http://timestamp.verisign.com/scripts/timstamp.dll pe Thing.exe -v -m "C:\MSSign32.dll"` 72 | 73 | .NET Core - binaries or self-compiled 74 | 75 | `safenetsign.exe 8cfaa82e5c3842ffee22d82d8ff812b3a1de1ebb my-container-name 12345678 user http://timestamp.verisign.com/scripts/timstamp.dll pe Thing.exe -v -m "C:\MSSign32.dll"` 76 | 77 | ## Contributing 78 | I wrote the original code to solve a very specific problem I faced in my day-to-day work. Over time, it gathered a small amount of interest so I decided to rewrite the whole thing and make it a little bit more official (and flexible AND user friendly). 79 | 80 | I'm open to contributions, be it a PR or an issue and I'll do my best to answer you requests and questions. 81 | 82 | ## C++ version 83 | You can also find the precursor of this tool in this repo, written entirely in C++ (VS 2017 or 2019 with C++ workload required to compile). __This version is no longer maintained and does not support all the options of the .NET version__. 84 | 85 | ## Limitations 86 | This code is provided without any guuarantees. We use it as part of our production-grade CI pipeline (self-hosted Azure DevOps build agent on a physical Windows 10 machine) and it performs as expected. Your milage may vary but if you give the tool a try and find an issue, please report it and I'll do my best to fix it. 87 | 88 | ## Thanks where it's due 89 | It was only possible to create this tool because of SO users [draketb](https://stackoverflow.com/users/1751253/draketb) and [RbMm](https://stackoverflow.com/users/6401656/rbmm) (and others). So big thanks to them and the whole SO community! 90 | -------------------------------------------------------------------------------- /src/SafenetSign/CertificateStore.cs: -------------------------------------------------------------------------------- 1 | namespace SafenetSign 2 | { 3 | public enum CertificateStore 4 | { 5 | User, 6 | Machine 7 | } 8 | } -------------------------------------------------------------------------------- /src/SafenetSign/CodeSigner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Runtime.InteropServices; 4 | using System.Text.RegularExpressions; 5 | using SafenetSign.Native; 6 | 7 | namespace SafenetSign 8 | { 9 | public static class CodeSigner 10 | { 11 | public static void SignFile(string certificateThumbprint, string pin, string containerName, 12 | CertificateStore store, string path, string timestampUrl, SignMode mode, string dllPath, Logger logger) 13 | { 14 | logger.WriteLine("Validating certificate thumbprint", true); 15 | if (certificateThumbprint?.Length != 40 || !ValidateThumbprint(certificateThumbprint)) 16 | { 17 | throw new SigningException( 18 | $"Invalid certificate thumbprint provided: {certificateThumbprint}. The thumbprint must be a valid SHA1 thumbprint - 40 characters long and consisting of only hexadecimal characters (0-9 and A-F)"); 19 | } 20 | 21 | logger.WriteLine("Converting thumbprint to bytes", true); 22 | var binaryHash = StringToByteArray(certificateThumbprint); 23 | 24 | var cryptoProvider = AcquireContext(containerName, Constants.CryptoProviderName, logger); 25 | try 26 | { 27 | SetPin(cryptoProvider, pin, logger); 28 | 29 | var certStore = OpenCertificateStore(store, logger); 30 | try 31 | { 32 | var certificate = RetrieveCertificate(binaryHash, certStore, out var hashHandle, out var hashBlobHandle, logger); 33 | 34 | try 35 | { 36 | SignFile(certificate, path, timestampUrl, mode, containerName, dllPath, logger); 37 | } 38 | finally 39 | { 40 | hashHandle?.Free(); 41 | hashBlobHandle?.Free(); 42 | NativeMethods.CertFreeCertificateContext(certificate); 43 | } 44 | } 45 | finally 46 | { 47 | NativeMethods.CertCloseStore(certStore, 0); 48 | } 49 | } 50 | finally 51 | { 52 | NativeMethods.CryptReleaseContext(cryptoProvider, 0); 53 | } 54 | } 55 | 56 | private static IntPtr OpenCertificateStore(CertificateStore store, Logger logger) 57 | { 58 | var systemStore = GetSystemStore(store); 59 | 60 | logger.WriteLine($"Opening system-level cryptographic store {systemStore}/{Constants.CryptoStoreName}", true); 61 | var certStore = NativeMethods.CertOpenStore(new IntPtr(Constants.CERT_STORE_PROV_SYSTEM), 62 | Constants.DONT_CARE, IntPtr.Zero, systemStore, Constants.CryptoStoreName); 63 | 64 | if (certStore == IntPtr.Zero) 65 | { 66 | var errorResult = Marshal.GetHRForLastWin32Error(); 67 | throw new SigningException($"Win32 error in CertOpenStore: {errorResult}", 68 | Marshal.GetExceptionForHR(errorResult)); 69 | } 70 | 71 | return certStore; 72 | } 73 | 74 | private static uint GetSystemStore(CertificateStore store) 75 | { 76 | uint storeToUse; 77 | switch (store) 78 | { 79 | case CertificateStore.User: 80 | storeToUse = Constants.CERT_SYSTEM_STORE_CURRENT_USER; 81 | break; 82 | case CertificateStore.Machine: 83 | storeToUse = Constants.CERT_SYSTEM_STORE_LOCAL_MACHINE; 84 | break; 85 | default: 86 | throw new SigningException($"Unknown {nameof(CertificateStore)} value encountered: {store}"); 87 | } 88 | 89 | return storeToUse; 90 | } 91 | 92 | private static bool ValidateThumbprint(string certificateThumbprint) 93 | { 94 | return Regex.IsMatch(certificateThumbprint, "[0-9A-Fa-f]{40}"); 95 | } 96 | 97 | private static byte[] StringToByteArray(string hex) 98 | { 99 | return Enumerable.Range(0, hex.Length) 100 | .Where(x => x % 2 == 0) 101 | .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) 102 | .ToArray(); 103 | } 104 | 105 | private static IntPtr RetrieveCertificate(byte[] binaryHash, IntPtr certStore, out GCHandle? hashHandle, out GCHandle? hashBlobHandle, Logger logger) 106 | { 107 | logger.WriteLine("Retrieving certificate from the store", true); 108 | hashHandle = GCHandle.Alloc(binaryHash, GCHandleType.Pinned); 109 | 110 | var blob = new CRYPTOAPI_BLOB { cbData = binaryHash.Length, pbData = hashHandle.Value.AddrOfPinnedObject() }; 111 | hashBlobHandle = GCHandle.Alloc(blob, GCHandleType.Pinned); 112 | 113 | var certificate = NativeMethods.CertFindCertificateInStore(certStore, Constants.MY_ENCODING_TYPE, 114 | Constants.DONT_CARE, 115 | Constants.CERT_FIND_SHA1_HASH, hashBlobHandle.Value.AddrOfPinnedObject(), IntPtr.Zero); 116 | 117 | if (certificate == IntPtr.Zero) 118 | { 119 | var errorResult = Marshal.GetHRForLastWin32Error(); 120 | throw new SigningException($"Win32 error in CertFindCertificateInStore: {errorResult}", Marshal.GetExceptionForHR(errorResult)); 121 | } 122 | 123 | return certificate; 124 | } 125 | 126 | private static void SignFile(IntPtr certificate, string path, string timestampUrl, SignMode type, string containerName, string dllPath, Logger logger) 127 | { 128 | logger.WriteLine("Beginning the signing process", true); 129 | var subjectInfo = GetSubjectInfoPointer(path); 130 | var signerCertificate = GetSignerCertificatePointer(certificate); 131 | var provider = GetProviderPointer(containerName); 132 | var signerSignEx2Params = GetSignerSignEx2ParametersPointer(timestampUrl, type, subjectInfo, signerCertificate, provider, out var signerSignHandle); 133 | 134 | try 135 | { 136 | IntPtr signModule; 137 | if (string.IsNullOrEmpty(dllPath)) 138 | { 139 | logger.WriteLine("Loading MSSign32.dll from default path", true); 140 | signModule = NativeMethods.LoadLibraryEx("MSSign32.dll", IntPtr.Zero, LoadLibraryFlags.LOAD_LIBRARY_SEARCH_SYSTEM32); 141 | } 142 | else 143 | { 144 | logger.WriteLine($"Loading MSSign32.dll from specific path: {dllPath}", true); 145 | signModule = NativeMethods.LoadLibraryEx(dllPath, IntPtr.Zero, LoadLibraryFlags.LOAD_WITH_ALTERED_SEARCH_PATH); 146 | } 147 | 148 | if (signModule == IntPtr.Zero) 149 | { 150 | var errorResult = Marshal.GetHRForLastWin32Error(); 151 | throw new SigningException($"Win32 error in LoadLibraryEx: {errorResult}", Marshal.GetExceptionForHR(errorResult)); 152 | } 153 | 154 | logger.WriteLine("Getting SignerSignEx2 pointer", true); 155 | var signerSignEx2Pointer = NativeMethods.GetProcAddress(signModule, "SignerSignEx2"); 156 | 157 | if (signModule == IntPtr.Zero) 158 | { 159 | var errorResult = Marshal.GetHRForLastWin32Error(); 160 | throw new SigningException($"Win32 error in GetProcAddress: {errorResult}", Marshal.GetExceptionForHR(errorResult)); 161 | } 162 | 163 | NativeMethods.SignerSignEx2Delegate signerSignEx2; 164 | 165 | try 166 | { 167 | logger.WriteLine("Marshalling SignerSignEx2 pointer to a delegate", true); 168 | signerSignEx2 = Marshal.GetDelegateForFunctionPointer( 169 | signerSignEx2Pointer); 170 | } 171 | catch (Exception e) 172 | { 173 | throw new SigningException("Error while marshalling SignerSignEx2 pointer to a managed delegate.", e); 174 | } 175 | 176 | logger.WriteLine("Invoking SignerSignEx2", true); 177 | var result = signerSignEx2(signerSignEx2Params.dwFlags, signerSignEx2Params.pSubjectInfo, 178 | signerSignEx2Params.pSigningCert, 179 | signerSignEx2Params.pSignatureInfo, 180 | signerSignEx2Params.pProviderInfo, 181 | signerSignEx2Params.dwTimestampFlags, 182 | signerSignEx2Params.pszAlgorithmOid, 183 | signerSignEx2Params.pwszTimestampURL, 184 | signerSignEx2Params.pCryptAttrs, 185 | signerSignEx2Params.pSipData, 186 | signerSignEx2Params.pSignerContext, 187 | signerSignEx2Params.pCryptoPolicy, 188 | signerSignEx2Params.pReserved); 189 | 190 | if (result != 0) 191 | { 192 | throw new SigningException("Win32 error in SignerSignEx2:", Marshal.GetExceptionForHR(result)); 193 | } 194 | 195 | logger.WriteLine("DONE", true); 196 | } 197 | finally 198 | { 199 | signerSignHandle?.Free(); 200 | } 201 | } 202 | 203 | private static IntPtr GetProviderPointer(string containerName) 204 | { 205 | var providerInfo = new SIGNER_PROVIDER_INFO 206 | { 207 | cbSize = (uint) Marshal.SizeOf(), 208 | pwszProviderName = Marshal.StringToHGlobalUni(Constants.CryptoProviderName), 209 | dwProviderType = Constants.PROV_RSA_FULL, 210 | dwPvkChoice = Constants.PVK_TYPE_KEYCONTAINER, 211 | PvkChoice = new SIGNER_PROVIDER_INFO.PvkChoiceUnion 212 | { 213 | pwszKeyContainer = Marshal.StringToHGlobalUni(containerName) 214 | } 215 | }; 216 | 217 | var providerHandle = Marshal.AllocHGlobal(Marshal.SizeOf()); 218 | Marshal.StructureToPtr(providerInfo, providerHandle, false); 219 | 220 | return providerHandle; 221 | } 222 | 223 | private static SIGNER_SIGN_EX2_PARAMS GetSignerSignEx2ParametersPointer(string timestampUrl, SignMode type, 224 | IntPtr subjectInfo, IntPtr signerCertificate, IntPtr provider, out GCHandle? signerSignHandle) 225 | { 226 | // signature info 227 | var signatureInfo = new SIGNER_SIGNATURE_INFO 228 | { 229 | cbSize = (uint)Marshal.SizeOf(), 230 | algidHash = Constants.CALG_SHA_256, 231 | dwAttrChoice = Constants.DONT_CARE, 232 | pAttrAuthCode = IntPtr.Zero, 233 | psAuthenticated = IntPtr.Zero, 234 | psUnauthenticated = IntPtr.Zero, 235 | }; 236 | 237 | var signatureHandle = Marshal.AllocHGlobal(Marshal.SizeOf()); 238 | Marshal.StructureToPtr(signatureInfo, signatureHandle, false); 239 | 240 | // signer sign ex params 241 | var signerSignEx2Params = new SIGNER_SIGN_EX2_PARAMS 242 | { 243 | dwFlags = Constants.DONT_CARE, 244 | pSubjectInfo = subjectInfo, 245 | pSigningCert = signerCertificate, 246 | pSignatureInfo = signatureHandle, 247 | pProviderInfo = provider, 248 | dwTimestampFlags = Constants.SIGNER_TIMESTAMP_AUTHENTICODE, 249 | pwszTimestampURL = Marshal.StringToHGlobalUni(timestampUrl) 250 | }; 251 | 252 | signerSignHandle = null; 253 | if (type == SignMode.APPX) 254 | { 255 | var sipData = new APPX_SIP_CLIENT_DATA(); 256 | signerSignHandle = GCHandle.Alloc(signerSignEx2Params, GCHandleType.Pinned); 257 | 258 | sipData.pSignerParams = signerSignHandle.Value.AddrOfPinnedObject(); 259 | 260 | var sipHandle = Marshal.AllocHGlobal(Marshal.SizeOf()); 261 | Marshal.StructureToPtr(sipData, sipHandle, false); 262 | 263 | signerSignEx2Params.pSipData = sipHandle; 264 | } 265 | 266 | return signerSignEx2Params; 267 | } 268 | 269 | private static IntPtr GetSignerCertificatePointer(IntPtr certificate) 270 | { 271 | // cert store info 272 | var certStoreInfo = new SIGNER_CERT_STORE_INFO 273 | { 274 | cbSize = (uint)Marshal.SizeOf(), 275 | dwCertPolicy = Constants.SIGNER_CERT_POLICY_CHAIN_NO_ROOT, 276 | pSigningCert = certificate, 277 | hCertStore = IntPtr.Zero 278 | }; 279 | 280 | // signer cert info 281 | var signerCertInfo = new SIGNER_CERT 282 | { 283 | cbSize = (uint)Marshal.SizeOf(), 284 | dwCertChoice = Constants.SIGNER_CERT_STORE, 285 | SignerCertSource = new SIGNER_CERT.SignerCertSourceUnion 286 | { pCertStoreInfo = Marshal.AllocHGlobal(Marshal.SizeOf()) } 287 | }; 288 | 289 | var certHandle = Marshal.AllocHGlobal(Marshal.SizeOf()); 290 | Marshal.StructureToPtr(signerCertInfo, certHandle, false); 291 | 292 | Marshal.StructureToPtr(certStoreInfo, signerCertInfo.SignerCertSource.pCertStoreInfo, false); 293 | return certHandle; 294 | } 295 | 296 | private static IntPtr GetSubjectInfoPointer(string path) 297 | { 298 | // target file info 299 | var signerFileInfo = new SIGNER_FILE_INFO 300 | { 301 | pwszFileName = Marshal.StringToHGlobalUni(path), 302 | cbSize = (uint)Marshal.SizeOf(), 303 | hFile = IntPtr.Zero 304 | }; 305 | 306 | // subject info 307 | var signerSubjectInfo = new SIGNER_SUBJECT_INFO 308 | { 309 | cbSize = (uint)Marshal.SizeOf(), 310 | pdwIndex = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(uint))), 311 | dwSubjectChoice = Constants.SIGNER_SUBJECT_FILE, 312 | SubjectChoice = new SIGNER_SUBJECT_INFO.SubjectChoiceUnion 313 | { pSignerFileInfo = Marshal.AllocHGlobal(Marshal.SizeOf()) } 314 | }; 315 | 316 | Marshal.StructureToPtr(Constants.DONT_CARE, signerSubjectInfo.pdwIndex, false); 317 | Marshal.StructureToPtr(signerFileInfo, signerSubjectInfo.SubjectChoice.pSignerFileInfo, false); 318 | 319 | var subjectHandle = Marshal.AllocHGlobal(Marshal.SizeOf()); 320 | Marshal.StructureToPtr(signerSubjectInfo, subjectHandle, false); 321 | return subjectHandle; 322 | } 323 | 324 | private static IntPtr AcquireContext(string containerName, string providerName, Logger logger) 325 | { 326 | var cryptoProvider = new IntPtr(); 327 | 328 | logger.WriteLine("Acquiring cryptographic context", true); 329 | if (!NativeMethods.CryptAcquireContext(ref cryptoProvider, containerName, 330 | providerName, Constants.PROV_RSA_FULL, Constants.CRYPT_SILENT)) 331 | { 332 | var errorResult = Marshal.GetHRForLastWin32Error(); 333 | throw new SigningException($"Win32 error in CryptAcquireContext: {errorResult}", Marshal.GetExceptionForHR(errorResult)); 334 | } 335 | 336 | return cryptoProvider; 337 | } 338 | 339 | private static void SetPin(IntPtr providerHandle, string tokenPin, Logger logger) 340 | { 341 | logger.WriteLine("Setting PIN", true); 342 | if (!NativeMethods.CryptSetProvParam(providerHandle, Constants.PP_SIGNATURE_PIN, 343 | System.Text.Encoding.UTF8.GetBytes(tokenPin), Constants.DONT_CARE)) 344 | { 345 | var errorResult = Marshal.GetHRForLastWin32Error(); 346 | throw new SigningException($"Win32 error in CryptSetProvParam: {errorResult}", Marshal.GetExceptionForHR(errorResult)); 347 | } 348 | } 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/SafenetSign/CommandParameters.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.ComponentModel.DataAnnotations; 3 | using Args; 4 | 5 | namespace SafenetSign 6 | { 7 | [ArgsModel(SwitchDelimiter = "-")] 8 | public class CommandParameters 9 | { 10 | [ArgsMemberSwitch(0)] 11 | [Required] 12 | [Description("The certificate thumbprint")] 13 | public string Thumbprint { get; set; } 14 | 15 | [ArgsMemberSwitch(1)] 16 | [Required] 17 | [Description("The name of the key container containing the private key")] 18 | public string PrivateKeyContainer { get; set; } 19 | 20 | [ArgsMemberSwitch(2)] 21 | [DefaultValue(CertificateStore.User)] 22 | [Required] 23 | [Description("The certificate store to search through (User or Machine)")] 24 | public CertificateStore Store { get; set; } 25 | 26 | [ArgsMemberSwitch(3)] 27 | [Required] 28 | [Description("The PIN protecting the private key")] 29 | public string Pin { get; set; } 30 | 31 | [ArgsMemberSwitch(4)] 32 | [Required] 33 | [Description("The timestamp URL")] 34 | public string TimestampUrl { get; set; } 35 | 36 | [ArgsMemberSwitch(5)] 37 | [DefaultValue(SignMode.PE)] 38 | [Required] 39 | [Description("The signing mode (PE or APPX)")] 40 | public SignMode Mode { get; set; } 41 | 42 | [ArgsMemberSwitch(6)] 43 | [Required] 44 | [Description("The path to the file to sign")] 45 | public string Path { get; set; } 46 | 47 | [ArgsMemberSwitch("m")] 48 | [Description("The path to the MSSign32.dll library")] 49 | public string MsSign32Path { get; set; } 50 | 51 | [ArgsMemberSwitch("v")] 52 | [Description("Whether to enable verbose logging")] 53 | public bool IsVerboseLoggingEnabled { get; set; } 54 | } 55 | } -------------------------------------------------------------------------------- /src/SafenetSign/Common.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/SafenetSign/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SafenetSign 4 | { 5 | public sealed class Logger 6 | { 7 | private readonly bool _isVerbose; 8 | 9 | public Logger(bool isVerbose) 10 | { 11 | _isVerbose = isVerbose; 12 | } 13 | 14 | public void WriteLine(string message, bool isVerbose) 15 | { 16 | if (isVerbose && !_isVerbose) 17 | { 18 | return; 19 | } 20 | 21 | Console.WriteLine(message); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/SafenetSign/Native/APPX_SIP_CLIENT_DATA.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SafenetSign.Native 4 | { 5 | public struct APPX_SIP_CLIENT_DATA 6 | { 7 | public IntPtr pSignerParams; 8 | public IntPtr pAppxSipState; 9 | } 10 | } -------------------------------------------------------------------------------- /src/SafenetSign/Native/CRYPTOAPI_BLOB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SafenetSign.Native 5 | { 6 | [StructLayout(LayoutKind.Sequential)] 7 | public struct CRYPTOAPI_BLOB 8 | { 9 | public int cbData; 10 | public IntPtr pbData; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/SafenetSign/Native/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace SafenetSign.Native 2 | { 3 | public static class Constants 4 | { 5 | public const uint PVK_TYPE_KEYCONTAINER = 2; 6 | 7 | public const uint PROV_RSA_FULL = 1; 8 | public const int CRYPT_SILENT = 0x00000040; 9 | 10 | public const uint CERT_STORE_PROV_SYSTEM = 10; 11 | 12 | public const uint DONT_CARE = 0; 13 | 14 | public const uint CERT_SYSTEM_STORE_CURRENT_USER = 1 << 16; 15 | public const uint CERT_SYSTEM_STORE_LOCAL_MACHINE = 2 << 16; 16 | 17 | public const int MY_ENCODING_TYPE = 0x00010000 | 0x00000001; 18 | public const int CERT_FIND_SHA1_HASH = 1 << 16; 19 | 20 | public const int SIGNER_SUBJECT_FILE = 0x01; 21 | public const int SIGNER_CERT_POLICY_CHAIN_NO_ROOT = 0x08; 22 | public const int SIGNER_CERT_STORE = 0x2; 23 | public const int CALG_SHA_256 = 0x0000800c; 24 | public const int SIGNER_TIMESTAMP_AUTHENTICODE = 1; 25 | 26 | public const int PP_SIGNATURE_PIN = 33; 27 | 28 | public const string CryptoProviderName = "eToken Base Cryptographic Provider"; 29 | public const string CryptoStoreName = "MY"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/SafenetSign/Native/LoadLibraryFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SafenetSign.Native 4 | { 5 | [Flags] 6 | public enum LoadLibraryFlags : uint 7 | { 8 | None = 0, 9 | DONT_RESOLVE_DLL_REFERENCES = 0x00000001, 10 | LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010, 11 | LOAD_LIBRARY_AS_DATAFILE = 0x00000002, 12 | LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040, 13 | LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020, 14 | LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200, 15 | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000, 16 | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100, 17 | LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800, 18 | LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400, 19 | LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 20 | } 21 | } -------------------------------------------------------------------------------- /src/SafenetSign/Native/SIGNER_CERT.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SafenetSign.Native 5 | { 6 | [StructLayout(LayoutKind.Sequential)] 7 | public struct SIGNER_CERT 8 | { 9 | public uint cbSize; 10 | 11 | public uint dwCertChoice; 12 | 13 | public SignerCertSourceUnion SignerCertSource; 14 | 15 | [StructLayout(LayoutKind.Explicit)] 16 | public struct SignerCertSourceUnion 17 | { 18 | [FieldOffset(0)] 19 | public IntPtr pwszSpcFile; 20 | 21 | [FieldOffset(0)] 22 | public IntPtr pCertStoreInfo; 23 | 24 | [FieldOffset(0)] 25 | public IntPtr pSpcChainInfo; 26 | } 27 | 28 | public IntPtr hwnd; 29 | } 30 | } -------------------------------------------------------------------------------- /src/SafenetSign/Native/SIGNER_CERT_STORE_INFO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SafenetSign.Native 5 | { 6 | [StructLayout(LayoutKind.Sequential)] 7 | public struct SIGNER_CERT_STORE_INFO 8 | { 9 | public uint cbSize; 10 | 11 | public IntPtr pSigningCert; 12 | 13 | public uint dwCertPolicy; 14 | 15 | public IntPtr hCertStore; 16 | } 17 | } -------------------------------------------------------------------------------- /src/SafenetSign/Native/SIGNER_FILE_INFO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SafenetSign.Native 5 | { 6 | [StructLayout(LayoutKind.Sequential)] 7 | public struct SIGNER_FILE_INFO 8 | { 9 | public uint cbSize; 10 | public IntPtr pwszFileName; 11 | public IntPtr hFile; 12 | } 13 | } -------------------------------------------------------------------------------- /src/SafenetSign/Native/SIGNER_PROVIDER_INFO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SafenetSign.Native 5 | { 6 | public struct SIGNER_PROVIDER_INFO 7 | { 8 | public uint cbSize; 9 | public IntPtr pwszProviderName; 10 | public uint dwProviderType; 11 | public uint dwKeySpec; 12 | public uint dwPvkChoice; 13 | public PvkChoiceUnion PvkChoice; 14 | 15 | [StructLayout(LayoutKind.Explicit)] 16 | public struct PvkChoiceUnion 17 | { 18 | [FieldOffset(0)] 19 | public IntPtr pwszPvkFileName; 20 | 21 | [FieldOffset(0)] 22 | public IntPtr pwszKeyContainer; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/SafenetSign/Native/SIGNER_SIGNATURE_INFO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SafenetSign.Native 5 | { 6 | [StructLayout(LayoutKind.Sequential)] 7 | public struct SIGNER_SIGNATURE_INFO 8 | { 9 | public uint cbSize; 10 | public uint algidHash; // ALG_ID 11 | public uint dwAttrChoice; 12 | public IntPtr pAttrAuthCode; 13 | public IntPtr psAuthenticated; // PCRYPT_ATTRIBUTES 14 | public IntPtr psUnauthenticated; // PCRYPT_ATTRIBUTES 15 | } 16 | } -------------------------------------------------------------------------------- /src/SafenetSign/Native/SIGNER_SIGN_EX2_PARAMS.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SafenetSign.Native 4 | { 5 | public struct SIGNER_SIGN_EX2_PARAMS 6 | { 7 | public uint dwFlags; 8 | public IntPtr pSubjectInfo; 9 | public IntPtr pSigningCert; 10 | public IntPtr pSignatureInfo; 11 | public IntPtr pProviderInfo; 12 | public uint dwTimestampFlags; 13 | public IntPtr pszAlgorithmOid; 14 | public IntPtr pwszTimestampURL; 15 | public IntPtr pCryptAttrs; 16 | public IntPtr pSipData; 17 | public IntPtr pSignerContext; 18 | public IntPtr pCryptoPolicy; 19 | public IntPtr pReserved; 20 | } 21 | } -------------------------------------------------------------------------------- /src/SafenetSign/Native/SIGNER_SUBJECT_INFO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SafenetSign.Native 5 | { 6 | [StructLayout(LayoutKind.Sequential)] 7 | public struct SIGNER_SUBJECT_INFO 8 | { 9 | /// DWORD->unsigned int 10 | public uint cbSize; 11 | 12 | /// DWORD* 13 | public IntPtr pdwIndex; 14 | 15 | /// DWORD->unsigned int 16 | public uint dwSubjectChoice; 17 | 18 | /// SubjectChoiceUnion 19 | public SubjectChoiceUnion SubjectChoice; 20 | 21 | [StructLayout(LayoutKind.Explicit)] 22 | public struct SubjectChoiceUnion 23 | { 24 | /// SIGNER_FILE_INFO* 25 | [FieldOffset(0)] 26 | public IntPtr pSignerFileInfo; 27 | 28 | /// SIGNER_BLOB_INFO* 29 | [FieldOffset(0)] 30 | public IntPtr pSignerBlobInfo; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/SafenetSign/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using SafenetSign.Native; 4 | 5 | namespace SafenetSign 6 | { 7 | public static class NativeMethods 8 | { 9 | [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] 10 | [return: MarshalAs(UnmanagedType.Bool)] 11 | public static extern bool CryptAcquireContext( 12 | ref IntPtr hProv, 13 | string pszContainer, 14 | string pszProvider, 15 | uint dwProvType, 16 | uint dwFlags); 17 | 18 | [DllImport("Advapi32.dll", EntryPoint = "CryptReleaseContext", CharSet = CharSet.Unicode, SetLastError = true)] 19 | public static extern bool CryptReleaseContext( 20 | IntPtr hProv, 21 | uint dwFlags); 22 | 23 | [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] 24 | [return: MarshalAs(UnmanagedType.Bool)] 25 | public static extern bool CryptSetProvParam( 26 | IntPtr hProv, 27 | uint dwParam, 28 | [In] byte[] pbData, 29 | uint dwFlags); 30 | 31 | [DllImport("CRYPT32.DLL", EntryPoint = "CertOpenStore", CharSet = CharSet.Unicode, SetLastError = true)] 32 | public static extern IntPtr CertOpenStore( 33 | IntPtr lpszStoreProvider, 34 | uint dwMsgAndCertEncodingType, 35 | IntPtr hCryptProv, 36 | uint dwFlags, 37 | string pvPara); 38 | 39 | [DllImport("CRYPT32.DLL", EntryPoint = "CertCloseStore", CharSet = CharSet.Auto, SetLastError = true)] 40 | [return: MarshalAs(UnmanagedType.Bool)] 41 | public static extern bool CertCloseStore(IntPtr storeProvider, uint flags); 42 | 43 | [DllImport("crypt32.dll", SetLastError = true)] 44 | public static extern IntPtr CertFindCertificateInStore( 45 | IntPtr hCertStore, 46 | uint dwCertEncodingType, 47 | uint dwFindFlags, 48 | uint dwFindType, 49 | IntPtr pszFindPara, 50 | IntPtr pPrevCertCntxt); 51 | 52 | [DllImport("crypt32.dll")] 53 | public static extern bool CertFreeCertificateContext(IntPtr pCertContext); 54 | 55 | [DllImport("kernel32.dll", SetLastError = true)] 56 | public static extern IntPtr LoadLibraryEx( 57 | string lpFileName, 58 | IntPtr hReservedNull, 59 | LoadLibraryFlags dwFlags); 60 | 61 | [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] 62 | public static extern IntPtr GetProcAddress( 63 | IntPtr hModule, 64 | string procName); 65 | 66 | public delegate int SignerSignEx2Delegate(uint dwFlags, IntPtr pSubjectInfo, IntPtr pSignerCert, IntPtr pSignatureInfo, 67 | IntPtr pProviderInfo, uint dwTimestampFlags, IntPtr pszTimestampAlgorithmOid, IntPtr pwszHttpTimeStamp, 68 | IntPtr psRequest, IntPtr pSipData, IntPtr ppSignerContext, IntPtr pCryptoPolicy, IntPtr pReserved); 69 | } 70 | } -------------------------------------------------------------------------------- /src/SafenetSign/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using Args; 7 | 8 | namespace SafenetSign 9 | { 10 | class Program 11 | { 12 | static int Main(string[] args) 13 | { 14 | if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.OSArchitecture != Architecture.X64) 15 | { 16 | Console.Error.WriteLine("This tool only supports x64 Windows"); 17 | return 10; 18 | } 19 | 20 | var argumentModel = Configuration.Configure(); 21 | 22 | CommandParameters command; 23 | try 24 | { 25 | command = argumentModel.CreateAndBind(args); 26 | 27 | if (!string.IsNullOrEmpty(command.MsSign32Path) && !Path.IsPathRooted(command.MsSign32Path)) 28 | { 29 | // https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibraryexa#searching-for-dlls-and-dependencies 30 | Console.Error.WriteLine("Specifying a relative path for the MSSign32.dll is not supported"); 31 | return 9; 32 | } 33 | } 34 | catch (Exception) 35 | { 36 | PrintHelp(argumentModel); 37 | 38 | return 1; 39 | } 40 | 41 | try 42 | { 43 | CodeSigner.SignFile(command.Thumbprint, command.Pin, command.PrivateKeyContainer, command.Store, 44 | command.Path, command.TimestampUrl, 45 | command.Mode, command.MsSign32Path, new Logger(command.IsVerboseLoggingEnabled)); 46 | 47 | return 0; 48 | } 49 | catch (SigningException ex) 50 | { 51 | Console.Error.WriteLine("Signing operation failed. Error details:"); 52 | Console.Error.WriteLine(ex.GetBaseException().Message); 53 | 54 | return 2; 55 | } 56 | } 57 | 58 | private static void PrintHelp(IModelBindingDefinition argumentModel) 59 | { 60 | Console.WriteLine("Invalid command line arguments specified."); 61 | Console.WriteLine(); 62 | 63 | var arguments = string.Join(" ", argumentModel.Members.Select(_ => $"<{_.MemberInfo.Name}>")); 64 | Console.WriteLine($"Usage: {arguments}"); 65 | Console.WriteLine(); 66 | 67 | var list = new List { new[] { "Argument", "Description", "Is required", "Default value" } }; 68 | 69 | foreach (var member in argumentModel.Members) 70 | { 71 | list.Add(new[] 72 | { 73 | member.MemberInfo.Name, member.HelpText, member.Required.ToString(), 74 | member.DefaultValue?.ToString() ?? string.Empty 75 | }); 76 | } 77 | 78 | var lengths = Enumerable.Range(0, list[0].Length).Select(_ => list.Max(a => a[_].Length)).ToList(); 79 | 80 | var rowWidth = lengths.Sum() + list[0].Length * 3 + 1; 81 | 82 | PrintRowSeparator(rowWidth); 83 | PrintHelpRow(list[0], lengths); 84 | PrintRowSeparator(rowWidth); 85 | 86 | foreach (var row in list.Skip(1)) 87 | { 88 | PrintHelpRow(row, lengths); 89 | } 90 | 91 | PrintRowSeparator(rowWidth); 92 | } 93 | 94 | private static void PrintRowSeparator(int rowWidth) 95 | { 96 | Console.WriteLine(new string('-', rowWidth)); 97 | } 98 | 99 | private static void PrintHelpRow(string[] row, List lengths) 100 | { 101 | for (var index = 0; index < row.Length; index++) 102 | { 103 | var s = row[index]; 104 | Console.Write($"| {s.PadRight(lengths[index])} "); 105 | } 106 | 107 | Console.WriteLine("|"); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/SafenetSign/SafenetSign.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | 7 | true 8 | safenet-sign 9 | ./nupkg 10 | 0.2.0 11 | 12 | 13 | 14 | false 15 | x64 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/SafenetSign/SafenetSignNoTool.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | win-x64 7 | SafenetSign 8 | SafenetSign 9 | 10 | 11 | 12 | false 13 | x64 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/SafenetSign/SignMode.cs: -------------------------------------------------------------------------------- 1 | namespace SafenetSign 2 | { 3 | public enum SignMode 4 | { 5 | PE, 6 | APPX 7 | } 8 | } -------------------------------------------------------------------------------- /src/SafenetSign/SigningException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SafenetSign 4 | { 5 | public sealed class SigningException : Exception 6 | { 7 | public SigningException(string message) : base(message) 8 | { 9 | } 10 | 11 | public SigningException(string message, Exception innerException) : base(message, innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SafenetSign/releasenotes.md: -------------------------------------------------------------------------------- 1 | ### New features 2 | 3 | * Verbose logging support (#5) 4 | * If you are having troubles with the tool, this might help you pinpoint the issue 5 | * Use non-default MSSign32.dll (#4) 6 | * You can use this option to specify where to load the singing library from 7 | * Improved command-line argumants handling (#3) 8 | 9 | ### Fixes 10 | * Improved native resource clean-up after signing 11 | 12 | ### Notes 13 | * This is the first release available as a dotnet global tool 14 | * A big thank you goes out to @Steinliiippp for helping me debug several issues (#1) -------------------------------------------------------------------------------- /src/Signer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.156 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Signer", "Signer\Signer.vcxproj", "{1988A2CB-C460-4FEB-A26B-76361E071D42}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafenetSign", "SafenetSign\SafenetSign.csproj", "{83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafenetSignNoTool", "SafenetSign\SafenetSignNoTool.csproj", "{B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | Release|Any CPU = Release|Any CPU 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {1988A2CB-C460-4FEB-A26B-76361E071D42}.Debug|Any CPU.ActiveCfg = Debug|Win32 23 | {1988A2CB-C460-4FEB-A26B-76361E071D42}.Debug|Any CPU.Build.0 = Debug|Win32 24 | {1988A2CB-C460-4FEB-A26B-76361E071D42}.Debug|x64.ActiveCfg = Debug|x64 25 | {1988A2CB-C460-4FEB-A26B-76361E071D42}.Debug|x64.Build.0 = Debug|x64 26 | {1988A2CB-C460-4FEB-A26B-76361E071D42}.Debug|x86.ActiveCfg = Debug|Win32 27 | {1988A2CB-C460-4FEB-A26B-76361E071D42}.Debug|x86.Build.0 = Debug|Win32 28 | {1988A2CB-C460-4FEB-A26B-76361E071D42}.Release|Any CPU.ActiveCfg = Release|Win32 29 | {1988A2CB-C460-4FEB-A26B-76361E071D42}.Release|x64.ActiveCfg = Release|x64 30 | {1988A2CB-C460-4FEB-A26B-76361E071D42}.Release|x64.Build.0 = Release|x64 31 | {1988A2CB-C460-4FEB-A26B-76361E071D42}.Release|x86.ActiveCfg = Release|Win32 32 | {1988A2CB-C460-4FEB-A26B-76361E071D42}.Release|x86.Build.0 = Release|Win32 33 | {83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}.Debug|x64.ActiveCfg = Debug|Any CPU 36 | {83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}.Debug|x64.Build.0 = Debug|Any CPU 37 | {83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}.Debug|x86.ActiveCfg = Debug|Any CPU 38 | {83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}.Debug|x86.Build.0 = Debug|Any CPU 39 | {83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}.Release|x64.ActiveCfg = Release|Any CPU 42 | {83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}.Release|x64.Build.0 = Release|Any CPU 43 | {83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}.Release|x86.ActiveCfg = Release|Any CPU 44 | {83C3E7A0-7B2B-43BF-BBB4-20E80EC73EE1}.Release|x86.Build.0 = Release|Any CPU 45 | {B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}.Debug|x64.ActiveCfg = Debug|Any CPU 48 | {B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}.Debug|x64.Build.0 = Debug|Any CPU 49 | {B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}.Debug|x86.ActiveCfg = Debug|Any CPU 50 | {B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}.Debug|x86.Build.0 = Debug|Any CPU 51 | {B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}.Release|x64.ActiveCfg = Release|Any CPU 54 | {B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}.Release|x64.Build.0 = Release|Any CPU 55 | {B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}.Release|x86.ActiveCfg = Release|Any CPU 56 | {B1B3DE23-041A-46B7-AFA3-BD0A467A01A8}.Release|x86.Build.0 = Release|Any CPU 57 | EndGlobalSection 58 | GlobalSection(SolutionProperties) = preSolution 59 | HideSolutionNode = FALSE 60 | EndGlobalSection 61 | GlobalSection(ExtensibilityGlobals) = postSolution 62 | SolutionGuid = {21240ADA-2629-467C-ACF9-ABFBF5439E40} 63 | EndGlobalSection 64 | EndGlobal 65 | -------------------------------------------------------------------------------- /src/Signer/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "Signer.h" 9 | #pragma comment (lib, "cryptui.lib") 10 | #pragma comment(lib, "crypt32.lib") 11 | 12 | // this code was adapted from 13 | // https://stackoverflow.com/questions/48804073/signing-an-appxbundle-using-cryptuiwizdigitalsign-api 14 | // https://msdn.microsoft.com/en-us/library/windows/desktop/jj835834(v=vs.85).aspx 15 | 16 | int wmain(int argc, wchar_t** argv) 17 | { 18 | if (argc < 7) 19 | { 20 | std::wcout << L"usage: signer.exe \n"; 21 | std::wcout << L"mode = (appx|pe)\n"; 22 | return 1; 23 | } 24 | 25 | const std::wstring certHash = argv[1]; 26 | const std::wstring containerName = argv[2]; 27 | const std::wstring tokenPin = argv[3]; 28 | const std::wstring timestampUrl = argv[4]; 29 | const std::wstring mode = argv[5]; 30 | const std::wstring fileToSign = argv[6]; 31 | 32 | if (mode.compare(L"appx") != 0 && mode.compare(L"pe") != 0) 33 | { 34 | std::wcout << L"usage: signer.exe \n"; 35 | std::wcout << L"mode = (appx|pe)\n"; 36 | return 1; 37 | } 38 | 39 | std::wcout << L"Certificate thumbprint: " << certHash << L"\n"; 40 | std::wcout << L"Timestamp URL: " << timestampUrl << L"\n"; 41 | std::wcout << L"File to sign: " << fileToSign << L"\n"; 42 | 43 | BYTE* pHashData = new byte[20]; 44 | DWORD cbBinary = 20, dwSkip = 0, dwFlags = 0; 45 | 46 | if (!CryptStringToBinary(certHash.c_str(), 40, CRYPT_STRING_HEXRAW, pHashData, &cbBinary, &dwSkip, &dwFlags)) 47 | { 48 | std::wcout << L"Converting the certificate thumbprint to byte array failed. Check the provided SHA1 data." << L"\n"; 49 | exit(1); 50 | } 51 | 52 | std::wcout << L"Logging into the token... "; 53 | HCRYPTPROV cryptoContext = UnlockToken(containerName, utf16_to_utf8(tokenPin)); 54 | 55 | if (cryptoContext) 56 | { 57 | std::wcout << L"Success" << L"\n"; 58 | } 59 | else 60 | { 61 | std::wcout << L"Failure!" << L"\n"; 62 | exit(1); 63 | } 64 | 65 | HCERTSTORE hSystemStore; 66 | PCCERT_CONTEXT pDesiredCert = NULL; 67 | PCCERT_CONTEXT pCertContext; 68 | 69 | if (hSystemStore = CertOpenStore( 70 | CERT_STORE_PROV_SYSTEM_W, 71 | 0, 72 | NULL, 73 | CERT_SYSTEM_STORE_CURRENT_USER, 74 | L"MY")) 75 | { 76 | std::wcout << L"Certificate store opened successfully" << L"\n"; 77 | } 78 | else 79 | { 80 | std::wcerr << L"Unable to open certificate store, error " << std::hex << std::showbase << ::GetLastError() << L"\n"; 81 | exit(1); 82 | } 83 | 84 | CRYPT_HASH_BLOB hashBlob = {}; 85 | hashBlob.cbData = 20; 86 | hashBlob.pbData = pHashData; 87 | 88 | if (pDesiredCert = CertFindCertificateInStore( 89 | hSystemStore, 90 | MY_ENCODING_TYPE, 91 | 0, 92 | CERT_FIND_SHA1_HASH, 93 | &hashBlob, 94 | NULL)) 95 | { 96 | std::wcout << L"Certificate found" << L"\n"; 97 | } 98 | else 99 | { 100 | std::wcerr << L"Unable to find the requested certificate (" << certHash << "), error " << std::hex << std::showbase << ::GetLastError() << L"\n"; 101 | exit(1); 102 | } 103 | 104 | std::wcout << L"Signing file " << fileToSign << "... "; 105 | 106 | HRESULT res = SignFile(pDesiredCert, fileToSign.c_str(), timestampUrl.c_str(), mode == L"appx"); 107 | 108 | if (res == S_OK) 109 | { 110 | std::wcout << L"Success" << L"\n"; 111 | } 112 | else 113 | { 114 | std::wcerr << L"Signing of the package failed in SignAppxPackage, error " << std::hex << std::showbase << ::GetLastError() << L"\n"; 115 | exit(1); 116 | } 117 | 118 | std::wcout << L"Signing complete\n"; 119 | 120 | if (hSystemStore) 121 | { 122 | CertCloseStore( 123 | hSystemStore, 124 | CERT_CLOSE_STORE_CHECK_FLAG); 125 | } 126 | 127 | if (pDesiredCert) 128 | { 129 | CertFreeCertificateContext(pDesiredCert); 130 | } 131 | 132 | if (cryptoContext) 133 | { 134 | CryptReleaseContext(cryptoContext, 0); 135 | } 136 | } 137 | 138 | HCRYPTPROV UnlockToken(const std::wstring& containerName, const std::string& tokenPin) 139 | { 140 | CryptProvHandle cryptProv; 141 | if (!::CryptAcquireContext(&cryptProv.Handle, containerName.c_str(), ETOKEN_BASE_CRYPT_PROV_NAME.c_str(), PROV_RSA_FULL, CRYPT_SILENT)) 142 | { 143 | std::wcerr << L"CryptAcquireContext failed, error " << std::hex << std::showbase << ::GetLastError() << L"\n"; 144 | return NULL; 145 | } 146 | 147 | if (!::CryptSetProvParam(cryptProv.Handle, PP_SIGNATURE_PIN, reinterpret_cast(tokenPin.c_str()), 0)) 148 | { 149 | std::wcerr << L"CryptSetProvParam failed, error " << std::hex << std::showbase << ::GetLastError() << L"\n"; 150 | return NULL; 151 | } 152 | 153 | auto result = cryptProv.Handle; 154 | cryptProv.Handle = NULL; 155 | return result; 156 | } 157 | 158 | HRESULT SignFile( 159 | _In_ PCCERT_CONTEXT signingCertContext, 160 | _In_ LPCWSTR packageFilePath, 161 | _In_ PCWSTR timestampUrl, 162 | _In_ BOOL isSigningAppx) 163 | { 164 | HRESULT hr = S_OK; 165 | 166 | // Initialize the parameters for SignerSignEx2 167 | DWORD signerIndex = 0; 168 | 169 | SIGNER_FILE_INFO fileInfo = {}; 170 | fileInfo.cbSize = sizeof(SIGNER_FILE_INFO); 171 | fileInfo.pwszFileName = packageFilePath; 172 | 173 | SIGNER_SUBJECT_INFO subjectInfo = {}; 174 | subjectInfo.cbSize = sizeof(SIGNER_SUBJECT_INFO); 175 | subjectInfo.pdwIndex = &signerIndex; 176 | subjectInfo.dwSubjectChoice = SIGNER_SUBJECT_FILE; 177 | subjectInfo.pSignerFileInfo = &fileInfo; 178 | 179 | SIGNER_CERT_STORE_INFO certStoreInfo = {}; 180 | certStoreInfo.cbSize = sizeof(SIGNER_CERT_STORE_INFO); 181 | certStoreInfo.dwCertPolicy = SIGNER_CERT_POLICY_CHAIN_NO_ROOT; 182 | certStoreInfo.pSigningCert = signingCertContext; 183 | 184 | SIGNER_CERT cert = {}; 185 | cert.cbSize = sizeof(SIGNER_CERT); 186 | cert.dwCertChoice = SIGNER_CERT_STORE; 187 | cert.pCertStoreInfo = &certStoreInfo; 188 | 189 | SIGNER_SIGNATURE_INFO signatureInfo = {}; 190 | signatureInfo.cbSize = sizeof(SIGNER_SIGNATURE_INFO); 191 | signatureInfo.algidHash = CALG_SHA_256; 192 | signatureInfo.dwAttrChoice = SIGNER_NO_ATTR; 193 | 194 | SIGNER_SIGN_EX2_PARAMS signerParams = {}; 195 | signerParams.pSubjectInfo = &subjectInfo; 196 | signerParams.pSigningCert = &cert; 197 | signerParams.pSignatureInfo = &signatureInfo; 198 | signerParams.dwTimestampFlags = SIGNER_TIMESTAMP_AUTHENTICODE; 199 | signerParams.pwszTimestampURL = timestampUrl; 200 | 201 | APPX_SIP_CLIENT_DATA sipClientData; 202 | if (isSigningAppx) 203 | { 204 | // we only use this when signing appx packages 205 | sipClientData = {}; 206 | sipClientData.pSignerParams = &signerParams; 207 | signerParams.pSipData = &sipClientData; 208 | } 209 | 210 | // Type definition for invoking SignerSignEx2 via GetProcAddress 211 | typedef HRESULT(WINAPI *SignerSignEx2Function)( 212 | DWORD, 213 | PSIGNER_SUBJECT_INFO, 214 | PSIGNER_CERT, 215 | PSIGNER_SIGNATURE_INFO, 216 | PSIGNER_PROVIDER_INFO, 217 | DWORD, 218 | PCSTR, 219 | PCWSTR, 220 | PCRYPT_ATTRIBUTES, 221 | PVOID, 222 | PSIGNER_CONTEXT *, 223 | PVOID, 224 | PVOID); 225 | 226 | // Load the SignerSignEx2 function from MSSign32.dll 227 | HMODULE msSignModule = LoadLibraryEx( 228 | L"MSSign32.dll", 229 | NULL, 230 | LOAD_LIBRARY_SEARCH_SYSTEM32); 231 | 232 | if (msSignModule) 233 | { 234 | SignerSignEx2Function SignerSignEx2 = reinterpret_cast( 235 | GetProcAddress(msSignModule, "SignerSignEx2")); 236 | if (SignerSignEx2) 237 | { 238 | hr = SignerSignEx2( 239 | signerParams.dwFlags, 240 | signerParams.pSubjectInfo, 241 | signerParams.pSigningCert, 242 | signerParams.pSignatureInfo, 243 | signerParams.pProviderInfo, 244 | signerParams.dwTimestampFlags, 245 | signerParams.pszAlgorithmOid, 246 | signerParams.pwszTimestampURL, 247 | signerParams.pCryptAttrs, 248 | signerParams.pSipData, 249 | signerParams.pSignerContext, 250 | signerParams.pCryptoPolicy, 251 | signerParams.pReserved); 252 | } 253 | else 254 | { 255 | DWORD lastError = GetLastError(); 256 | hr = HRESULT_FROM_WIN32(lastError); 257 | } 258 | 259 | FreeLibrary(msSignModule); 260 | } 261 | else 262 | { 263 | DWORD lastError = GetLastError(); 264 | hr = HRESULT_FROM_WIN32(lastError); 265 | } 266 | 267 | if (isSigningAppx && sipClientData.pAppxSipState) 268 | { 269 | sipClientData.pAppxSipState->Release(); 270 | } 271 | 272 | return hr; 273 | } 274 | 275 | std::string utf16_to_utf8(const std::wstring& str) 276 | { 277 | if (str.empty()) 278 | { 279 | return ""; 280 | } 281 | 282 | auto utf8len = ::WideCharToMultiByte(CP_UTF8, 0, str.data(), str.size(), NULL, 0, NULL, NULL); 283 | if (utf8len == 0) 284 | { 285 | return ""; 286 | } 287 | 288 | std::string utf8Str; 289 | utf8Str.resize(utf8len); 290 | ::WideCharToMultiByte(CP_UTF8, 0, str.data(), str.size(), &utf8Str[0], utf8Str.size(), NULL, NULL); 291 | 292 | return utf8Str; 293 | } -------------------------------------------------------------------------------- /src/Signer/Signer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define MY_ENCODING_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING) 4 | #define SIGNER_TIMESTAMP_AUTHENTICODE 1 5 | 6 | HRESULT SignFile(_In_ PCCERT_CONTEXT signingCertContext, _In_ LPCWSTR packageFilePath, PCWSTR timestampUrl, BOOL isSigningAppx); 7 | HCRYPTPROV UnlockToken(const std::wstring& containerName, const std::string& tokenPin); 8 | 9 | typedef struct _SIGNER_FILE_INFO 10 | { 11 | DWORD cbSize; 12 | LPCWSTR pwszFileName; 13 | HANDLE hFile; 14 | }SIGNER_FILE_INFO, *PSIGNER_FILE_INFO; 15 | 16 | typedef struct _SIGNER_BLOB_INFO 17 | { 18 | DWORD cbSize; 19 | GUID *pGuidSubject; 20 | DWORD cbBlob; 21 | BYTE *pbBlob; 22 | LPCWSTR pwszDisplayName; 23 | }SIGNER_BLOB_INFO, *PSIGNER_BLOB_INFO; 24 | 25 | typedef struct _SIGNER_SUBJECT_INFO 26 | { 27 | DWORD cbSize; 28 | DWORD *pdwIndex; 29 | DWORD dwSubjectChoice; 30 | union 31 | { 32 | SIGNER_FILE_INFO *pSignerFileInfo; 33 | SIGNER_BLOB_INFO *pSignerBlobInfo; 34 | }; 35 | }SIGNER_SUBJECT_INFO, *PSIGNER_SUBJECT_INFO; 36 | 37 | // dwSubjectChoice should be one of the following: 38 | #define SIGNER_SUBJECT_FILE 0x01 39 | #define SIGNER_SUBJECT_BLOB 0x02 40 | 41 | typedef struct _SIGNER_ATTR_AUTHCODE 42 | { 43 | DWORD cbSize; 44 | BOOL fCommercial; 45 | BOOL fIndividual; 46 | LPCWSTR pwszName; 47 | LPCWSTR pwszInfo; 48 | }SIGNER_ATTR_AUTHCODE, *PSIGNER_ATTR_AUTHCODE; 49 | 50 | typedef struct _SIGNER_SIGNATURE_INFO 51 | { 52 | DWORD cbSize; 53 | ALG_ID algidHash; 54 | DWORD dwAttrChoice; 55 | union 56 | { 57 | SIGNER_ATTR_AUTHCODE *pAttrAuthcode; 58 | }; 59 | PCRYPT_ATTRIBUTES psAuthenticated; 60 | PCRYPT_ATTRIBUTES psUnauthenticated; 61 | }SIGNER_SIGNATURE_INFO, *PSIGNER_SIGNATURE_INFO; 62 | 63 | // dwAttrChoice should be one of the following: 64 | #define SIGNER_NO_ATTR 0x00 65 | #define SIGNER_AUTHCODE_ATTR 0x01 66 | 67 | typedef struct _SIGNER_PROVIDER_INFO 68 | { 69 | DWORD cbSize; 70 | LPCWSTR pwszProviderName; 71 | DWORD dwProviderType; 72 | DWORD dwKeySpec; 73 | DWORD dwPvkChoice; 74 | union 75 | { 76 | LPWSTR pwszPvkFileName; 77 | LPWSTR pwszKeyContainer; 78 | }; 79 | }SIGNER_PROVIDER_INFO, *PSIGNER_PROVIDER_INFO; 80 | 81 | //dwPvkChoice should be one of the following: 82 | #define PVK_TYPE_FILE_NAME 0x01 83 | #define PVK_TYPE_KEYCONTAINER 0x02 84 | 85 | typedef struct _SIGNER_SPC_CHAIN_INFO 86 | { 87 | DWORD cbSize; 88 | LPCWSTR pwszSpcFile; 89 | DWORD dwCertPolicy; 90 | HCERTSTORE hCertStore; 91 | }SIGNER_SPC_CHAIN_INFO, *PSIGNER_SPC_CHAIN_INFO; 92 | 93 | typedef struct _SIGNER_CERT_STORE_INFO 94 | { 95 | DWORD cbSize; 96 | PCCERT_CONTEXT pSigningCert; 97 | DWORD dwCertPolicy; 98 | HCERTSTORE hCertStore; 99 | }SIGNER_CERT_STORE_INFO, *PSIGNER_CERT_STORE_INFO; 100 | 101 | //dwCertPolicy can be a combination of the following flags: 102 | #define SIGNER_CERT_POLICY_STORE 0x01 103 | #define SIGNER_CERT_POLICY_CHAIN 0x02 104 | #define SIGNER_CERT_POLICY_SPC 0x04 105 | #define SIGNER_CERT_POLICY_CHAIN_NO_ROOT 0x08 106 | 107 | typedef struct _SIGNER_CERT 108 | { 109 | DWORD cbSize; 110 | DWORD dwCertChoice; 111 | union 112 | { 113 | LPCWSTR pwszSpcFile; 114 | SIGNER_CERT_STORE_INFO *pCertStoreInfo; 115 | SIGNER_SPC_CHAIN_INFO *pSpcChainInfo; 116 | }; 117 | HWND hwnd; 118 | }SIGNER_CERT, *PSIGNER_CERT; 119 | 120 | //dwCertChoice should be one of the following 121 | #define SIGNER_CERT_SPC_FILE 0x01 122 | #define SIGNER_CERT_STORE 0x02 123 | #define SIGNER_CERT_SPC_CHAIN 0x03 124 | 125 | typedef struct _SIGNER_CONTEXT 126 | { 127 | DWORD cbSize; 128 | DWORD cbBlob; 129 | BYTE *pbBlob; 130 | }SIGNER_CONTEXT, *PSIGNER_CONTEXT; 131 | 132 | typedef struct _SIGNER_SIGN_EX2_PARAMS 133 | { 134 | DWORD dwFlags; 135 | PSIGNER_SUBJECT_INFO pSubjectInfo; 136 | PSIGNER_CERT pSigningCert; 137 | PSIGNER_SIGNATURE_INFO pSignatureInfo; 138 | PSIGNER_PROVIDER_INFO pProviderInfo; 139 | DWORD dwTimestampFlags; 140 | PCSTR pszAlgorithmOid; 141 | PCWSTR pwszTimestampURL; 142 | PCRYPT_ATTRIBUTES pCryptAttrs; 143 | PVOID pSipData; 144 | PSIGNER_CONTEXT *pSignerContext; 145 | PVOID pCryptoPolicy; 146 | PVOID pReserved; 147 | } SIGNER_SIGN_EX2_PARAMS, *PSIGNER_SIGN_EX2_PARAMS; 148 | 149 | typedef struct _APPX_SIP_CLIENT_DATA 150 | { 151 | PSIGNER_SIGN_EX2_PARAMS pSignerParams; 152 | IUnknown* pAppxSipState; 153 | } APPX_SIP_CLIENT_DATA, *PAPPX_SIP_CLIENT_DATA; 154 | 155 | struct CryptProvHandle 156 | { 157 | HCRYPTPROV Handle = NULL; 158 | CryptProvHandle(HCRYPTPROV handle = NULL) : Handle(handle) {} 159 | ~CryptProvHandle() { if (Handle) ::CryptReleaseContext(Handle, 0); } 160 | }; 161 | 162 | const std::wstring ETOKEN_BASE_CRYPT_PROV_NAME = L"eToken Base Cryptographic Provider"; 163 | 164 | std::string utf16_to_utf8(const std::wstring& str); -------------------------------------------------------------------------------- /src/Signer/Signer.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {1988A2CB-C460-4FEB-A26B-76361E071D42} 24 | Signer 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v142 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | Disabled 77 | true 78 | true 79 | 80 | 81 | 82 | 83 | Level3 84 | Disabled 85 | true 86 | true 87 | 88 | 89 | 90 | 91 | Level3 92 | MaxSpeed 93 | true 94 | true 95 | true 96 | true 97 | 98 | 99 | true 100 | true 101 | 102 | 103 | 104 | 105 | Level3 106 | MaxSpeed 107 | true 108 | true 109 | true 110 | true 111 | 112 | 113 | true 114 | true 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/Signer/Signer.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | 23 | 24 | Header Files 25 | 26 | 27 | --------------------------------------------------------------------------------