Make people trust your NuGet Packages more with code signing!
Tags: devops, azure, dotnet, security
Published: 2019-04-05 15:58:36

---
title: Make people trust your NuGet Packages more with code signing!
published: true
description: Want your packages to be trusted with code-signing but do it fast in your devops pipeline?
tags: devops,azure,dotnet,security
canonical_url: http://timheuer.com/blog/archive/2019/04/04/signing-nuget-package-using-azure-devops-pipelines.aspx
cover_image: https://thepracticaldev.s3.amazonaws.com/i/wuttxgbxkh8frldle0s5.png
---
<p>Like most things in life, this all started with someone’s message on Twitter.</p><p><blockquote class="twitter-tweet" data-conversation="none"><p lang="en" dir="ltr">Blogged: Why NuGet Package Signing Is Not (Yet) for Me. <a href="https://t.co/4RSes2Bo4r">https://t.co/4RSes2Bo4r</a><br>A close look at NuGet package signing feature, where it's useful, and in which ways it falls short.</p>— Boom Haackalacka (@haacked) <a href="https://twitter.com/haacked/status/1113477770284126209?ref_src=twsrc%5Etfw">April 3, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p><em>You can code-sign a NuGet Package?</em>&nbsp; I don’t know why I didn’t know that other than I figured I had no idea that I should care or have even given a second thought to it.&nbsp; But Phil’s article and subsequent discussion on Twitter made me just realize I should take a look at the flow.&nbsp; After all more sage wisdom from Phil kicked me over the edge:</p><p><blockquote class="twitter-tweet" data-conversation="none"><p lang="en" dir="ltr">There’s no harm to anyone if you do sign your package.</p>— Boom Haackalacka (@haacked) <a href="https://twitter.com/haacked/status/1113504159376990208?ref_src=twsrc%5Etfw">April 3, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>You’re right Phil, and how can you argue with that forehead…so let’s do this.&nbsp; </p><blockquote><p><em><strong>What is NuGet?</strong></em> NuGet is a package management system that was primarily developed for .NET developers and has now become a de-facto package/release mechanism for that ecosystem.&nbsp; What npm is to Node.js developers, NuGet is to .NET developers.&nbsp; More info at <a href="https://www.nuget.org">https://www.nuget.org</a>.&nbsp; </p></blockquote><p>I’ve got a little library for helping .NET developers be more productive with creating Alexa apps, <a href="https://www.nuget.org/packages/Alexa.NET/">Alexa.NET</a>.&nbsp; When I started this project I used to just have this on my local box and would build things using Visual Studio and manually upload the NuGet package.&nbsp; Then people made fun of me.&nbsp; And I ate my sorrows in boxes of Moon Pies.&nbsp; Luckily I work with a bunch of talented folks and helped me see the light in DevOps and helped me establish a CI/CD pipeline using <a href="https://azure.microsoft.com/solutions/devops/?WT.mc_id=acom-blog-timheuer">Azure DevOps</a>.&nbsp; Since then I’ve got my library building, a release approval flow, automated packaging/publishing to the NuGet servers.&nbsp; I simply just check-in code and a new version is released.&nbsp; Perfect.&nbsp; Now I just want to add code-signing to the package.&nbsp; Naturally I do what every professional developer does and <strike>Google’d</strike> went to read the docs about code signing NuGet packages.&nbsp; Luckily there is some pretty good documentation on <a href="https://docs.microsoft.com/nuget/create-packages/sign-a-package?WT.mc_id=docs-blog-timheuer">Signing NuGet Packages</a>!&nbsp; </p><p>The first thing you need is a code-signing certificate.&nbsp; There are many providers of these and different prices so pick your preferred provider.&nbsp; I chose to use <strong><a href="https://www.digicert.com">DigiCert</a></strong> for this one but have used other providers in the past.&nbsp; The process for getting a code-signing cert is a bit more than just an average SSL certificate so be sure to follow the steps carefully.&nbsp; Once you have that in place, export the DER and PFX versions as you will need both of these for this process.&nbsp; Your provider should provide instructions on how to do this for you.</p><p>Next up was to modify my Azure DevOps Pipeline.&nbsp; I do my NuGet activity in a Release pipeline after a successful build and an approval step to actually complete the deployment.&nbsp; My simple release pipeline looks like this:</p><p><img title="" style="margin-right: auto; margin-left: auto; float: none; display: block;" alt="Screenshot of Azure DevOps NuGet Push definition" src="https://storage2.timheuer.com/nugetsign-initialpipeline.png"></p><p>The signing is provided by the NuGet CLI and so I just needed to add another task to this flow right?&nbsp; I added another one in and was going to just choose the ‘sign’ command as the configured option.&nbsp; Well, the ‘sign’ command isn’t a selectable option in the task.&nbsp; There is a <a href="https://github.com/Microsoft/azure-pipelines-tasks/issues/7386?WT.mc_id=github-blog-timheuer">request to make this one of the default options for the Azure Pipeline Task</a> right now but it isn’t in there yet.&nbsp; So for this we will use the ‘custom’ option which allows us to pass in any command and args.&nbsp; The docs already told me the commands that I would need: a certificate file being the minimum I would have to have.&nbsp; Hmm, how am I going to have a certificate file in my CD pipeline?!&nbsp; As it turns out there is a <a href="https://docs.microsoft.com/azure/devops/pipelines/library/secure-files?view=azure-devops&amp;WT.mc_id=docs-blog-timheuer">secure file storage in Azure Pipelines</a> I can use! This allows me to upload a file that I can later reference in a pipeline using an argument.&nbsp; Remember that PFX file we exported?&nbsp; In your DevOps project under Pipelines there is a ‘Library’ menu option.&nbsp; Going there takes you to where you can upload files and I uploaded my PFX file there:</p><p><img title="" style="margin-right: auto; margin-left: auto; float: none; display: block;" alt="Screenshot of Azure DevOps Library" src="https://storage2.timheuer.com/nugetsign-securefiles.png"></p><p>The next thing I need to do is also provide my password for that exported PFX file (you did export it with a password right!).&nbsp; To do this I made use of <a href="https://docs.microsoft.com/azure/devops/pipelines/library/variable-groups?view=azure-devops&amp;tabs=yaml&amp;WT.mc_id=docs-blog-timheuer">variable groups</a> in Azure DevOps, created a group called CertificateValues and added my name/value pair there, marking the value as a secret.&nbsp; As a variable group I can ‘link’ this group to any build/release definition without explicitly having the variable in those definitions.&nbsp; This is super handy to share across definitions.&nbsp; You can now <a href="https://docs.microsoft.com/azure/devops/pipelines/library/variable-groups?tabs=yaml&amp;amp;%3Bview=azure-devops&amp;view=azure-devops&amp;WT.mc_id=docs-blog-timheuer#link-secrets-from-an-azure-key-vault">link to an Azure KeyVault</a> for secrets (more to come on that in a part 2 blog post here).&nbsp; I’ve got my code signing cert (PFX) and my certificate password stored securely.&nbsp; With these two things now I’m ready to continue on my definition.</p><p>Now how will I get the file from the secure storage?!&nbsp; As I read in the docs, there is a <a href="https://docs.microsoft.com/azure/devops/pipelines/tasks/utility/download-secure-file?view=azure-devops&amp;WT.mc_id=docs-blog-timheuer">Download Secure File</a> task that I can add to my pipeline.&nbsp; The configuration asks me what file to use and then in the Reference Name of the Output Variables area I give it a name I can use, in this case ‘Certificate’:</p><p><img title="" style="margin-right: auto; margin-left: auto; float: none; display: block;" alt="Screenshot of Azure DevOps Download Secure File" src="https://storage2.timheuer.com/nugetsign-downloadfile.png"></p><p>This variable name allows me to use it later in my definitions as $(Certificate.secureFilePath) so I don’t have to fiddle around guessing where it downloaded to on the agent machine.&nbsp; Now that we have that figured out, let’s move back to the signing task…remember that ‘custom’ one we talked about earlier.&nbsp; In the custom task I specify in the Command and Arguments section the full command + arguments I need according to the docs.&nbsp; My full definition looks like this:</p><p><pre class="brush: bash;">sign $(System.ArtifactsDirectory)\$(Release.PrimaryArtifactSourceAlias)\drop\*.nupkg 
    -CertificatePath $(Certificate.secureFilePath) 
    -CertificatePassword $(CertificatePassword)  
    -Timestamper http://timestamp.digicert.com
</pre><p>To explain a bit I’m using some pre-defined variables System.ArtifactsDirectory and Release.PrimaryArtifactSourceAlias to help build the path to where the drop folder is on the agent machine.&nbsp; The others are from the secure files (Certificate.secureFilePath) and variable group (CertificatePassword) previously defined.&nbsp; These translate to real values in the build (the secret is masked in the logs as shown below) and complete the task.&nbsp; </p><p><img title="" style="margin-right: auto; margin-left: auto; float: none; display: block;" alt="Screenshot of Azure DevOps release definition" src="https://storage2.timheuer.com/nugetsign-finaldefinition.png"></p><p>Here was my log output from today in fact:</p><p><pre class="brush: bash;">2019-04-04T19:10:44.4785575Z ##[debug]exec tool: C:\hostedtoolcache\windows\NuGet\4.6.4\x64\nuget.exe
2019-04-04T19:10:44.4785807Z ##[debug]arguments:
2019-04-04T19:10:44.4786015Z ##[debug]   sign
2019-04-04T19:10:44.4786248Z ##[debug]   D:\a\r1\a\_Alexa.NET-master\drop\*.nupkg
2019-04-04T19:10:44.4786476Z ##[debug]   -CertificatePath
2019-04-04T19:10:44.4786687Z ##[debug]   D:\a\_temp\timheuer-digicert.pfx
2019-04-04T19:10:44.4786916Z ##[debug]   -CertificatePassword
2019-04-04T19:10:44.4787190Z ##[debug]   ***
2019-04-04T19:10:44.4787449Z ##[debug]   -Timestamper
2019-04-04T19:10:44.4787968Z ##[debug]   http://timestamp.digicert.com
2019-04-04T19:10:44.4789380Z ##[debug]   -NonInteractive
2019-04-04T19:10:44.4789939Z [command]C:\hostedtoolcache\windows\NuGet\4.6.4\x64\nuget.exe sign D:\a\r1\a\_Alexa.NET-master\drop\*.nupkg -CertificatePath D:\a\_temp\timheuer-digicert.pfx -CertificatePassword *** -Timestamper http://timestamp.digicert.com -NonInteractive
2019-04-04T19:10:52.6357013Z 
2019-04-04T19:10:52.6357916Z 
2019-04-04T19:10:52.6358659Z Signing package(s) with certificate:
&lt;snip to remove cert data&gt;
2019-04-04T19:10:52.6360408Z Valid from: 4/4/2019 12:00:00 AM to 4/7/2020 12:00:00 PM
2019-04-04T19:10:52.6360664Z 
2019-04-04T19:10:52.6360936Z Timestamping package(s) with:
2019-04-04T19:10:52.6361268Z http://timestamp.digicert.com
2019-04-04T19:10:52.6361576Z Package(s) signed successfully.
</pre><p>Done!&nbsp; Some simple added tasks and reading a few docs to get me having a signed NuGet package.&nbsp; Now re-reading the docs on signed packages I have to upload my certificate to my NuGet profile to get it to be recognized.&nbsp; This time I only need to provide the DER export.&nbsp; Once provided and my package is published, I get a little badge next to the listing showing me that this is a signed package:</p><p><img title="" style="margin-right: auto; margin-left: auto; float: none; display: block;" alt="Screenshot of NuGet version history listing" src="https://storage2.timheuer.com/nugetsign-versionlistingbadge.png"></p><p>This was a good exercise in helping me learn a few extra steps in Azure DevOps working with files and custom task variables.&nbsp; Immediately as I was doing this, my friend <strong><a href="https://twitter.com/onovotny">Oren Novotny</a></strong> couldn’t help but chastise me for this approach.&nbsp; </p><p><blockquote class="twitter-tweet"><p lang="und" dir="ltr">I am</p>— Oren Novotny (@onovotny) <a href="https://twitter.com/onovotny/status/1113549506551996418?ref_src=twsrc%5Etfw">April 3, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>So stay tuned for a secondary approach using <strong><a href="https://azure.microsoft.com/services/key-vault/?WT.mc_id=acom-blog-timheuer">Azure KeyVault</a></strong> completely to complete this without having to upload a certificate file.</p><br/><br/><p>(this article <a href="http://timheuer.com/blog/archive/2019/04/04/signing-nuget-package-using-azure-devops-pipelines.aspx">cross-posted from timheuer.com</a>)</p>