libgit2 / libgit2sharp

Git + .NET = ❤
http://libgit2.github.com
MIT License
3.18k stars 888 forks source link

Sign LibGit2Sharp.dll by a strong name #212

Closed kostrse closed 12 years ago

kostrse commented 12 years ago

VSPackage (Visual Studio extensions) development requires usage of strongly-named assemblies. As result, NuGet package version of the library cannot be used out of box in such scenarios.

It would be very helpful to add singing of the library on the build server and distribute the signed version. As it would solve my problem, also it give ability to distinguish the "official" releases from custom built binaries by other members of community.

The process of signing can be made absolutely transparent: private key can be stored on a build server, as well as *.csproj files can be patched with necessary strong name properties directly during CI build.

I can provide all the necessary information how to implement this and help with modification of MSBuild script.

Thanks,

nulltoken commented 12 years ago

It would be very helpful to add singing of the library on the build server and distribute the signed version.

@kostrse Hmm. I'm really not sure about this... It looks like the community hasn't reach a clear-cut agreement on this:

I can provide all the necessary information how to implement this and help with modification of MSBuild script.

I'm very far from being an expert on this subject, so provided some agreement is being reached on this topic, I'll be really thankful for all the help you'll be able to provide this project with :)

anaisbetts commented 12 years ago

If you need strongly named assemblies, you'll have to do it yourself. Strong Naming causes a ton of issues with open-source software and impedes contribution.

Think of the kittens Sergey. The kittens.

anaisbetts commented 12 years ago

So, on a more helpful and less "Nope"y note, the easiest way for you to get this working is to fork and git submodule libgit2sharp, then just add it to your build. That way you can set up libgit2sharp to be strongly named using your own key without spending hours fiddling with post-build stuff.

It's also a reasonable idea because libgit2 / libgit2sharp move at a pretty quick rate of development, so it's nice to have new features faster (and to be able to request bug fixes then quickly incorporate them).

Let me know if the submodule stuff doesn't make any sense and we can help you out to get this working

JamesNK commented 12 years ago

How to keep the most people happy: Sign the assembly, don't change the AssemblyVersionAttribute and use AssemblyFileVersionAttribute to represent the current version instead.

No assembly binding redirect issues and people who need a strong named assembly have it.

jaredpar commented 12 years ago

@kostrse the Visual Studio Package infrastructure doesn't require a strong name. The default template for packages creates one but it's actually unnecessary. Once the wizard finishes you can open up the project and delete all references to strong naming and the project will continue to run.

There are indeed cases where strong naming is necessary in a Visual Studio extension. But this is generally only if you have a utility library that is versioned. Even then the package consuming the library doesn't need to be versioned, only the library itself does

nulltoken commented 12 years ago

@xpaulbettsx @JamesNK @jaredpar Thanks a lot for your feedback!

@kostrse I'm afraid LibGit2Sharp assembly won't be strong-named in the near future. However, from what I read you should be able to work around this quite easily.

Closing this now.

deadlydog commented 9 years ago

For others who encounter this issue and are disappointed too, this is how you can sign the assembly yourself: http://www.codeproject.com/Tips/341645/Referenced-assembly-does-not-have-a-strong-name

anaisbetts commented 9 years ago

I also offer strong naming as a service, should you not want to handle this issue yourself:

http://log.paulbetts.org/a-modest-proposal-strong-naming-carbon-offsets/

deadlydog commented 9 years ago

Turns out that every few builds the assembly would somehow lose it's strongly signed status for some reason and I would have to strongly sign it again. I ended up just adding the following code to the "Pre-build event command line:" box in the project property page's Build Events tab to sign the assembly before every build.

:: Strongly sign the LibGit2Sharp.dll, as every few builds it gets overwritten, and VS Extensions want strongly signed assemblies.
:: http://www.codeproject.com/Tips/341645/Referenced-assembly-does-not-have-a-strong-name
cd "$(SolutionDir)packages\LibGit2Sharp.0.20.0.0\lib\net40\"
"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\ildasm.exe" /all /out=LibGit2Sharp.il LibGit2Sharp.dll
"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\sn.exe" -k MyLibGit2SharpKey.snk
"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ilasm.exe" /dll /key=MyLibGit2SharpKey.snk LibGit2Sharp.il

Notice that the LibGit2Sharp NuGet package version is hard-coded in here, so this code will need to be updated each time you update the NuGet package. Also, the paths for ildasm.exe, sn.exe, and ilasm.exe are where they were located on my machine; they may be located somewhere else on your machine.

nulltoken commented 9 years ago

@deadlydog Thanks for sharing this with us :sparkles:

@paulcbetts :cool: Do you foresee any price cut for Black Friday?

anaisbetts commented 9 years ago

@nulltoken Prices are double on Black Friday

AArnott commented 9 years ago

I'd also very much like to see this library strong-name signed so that it can be used from other strong-name signed projects.

But I wouldn't go to any of the trouble originally proposed to "transparently sign on the CI". Just check in the unencrypted private key with the source. That way, everyone can continue to build and use the source in place of the "official" one, as open source folks like. I challenge anyone who disagrees to please state the concrete things that break (or @paulcbetts, show me the kittens) or become harder to deal with if this project were to be strong-name signed.

terrajobst commented 9 years ago

I agree with @AArnott. In fact, checking in the key is inline with our official guidance now:

If you are an open-source developer and you want the identity benefits of a strong-named assembly, consider checking in the private key associated with an assembly into your source control system.

carlosmn commented 9 years ago

As we don't care about the GAC, there's only two things that SN would do for us. One is allow SN assemblies to depend on libgit2sharp, and the other is loading multiple versions of libgit2sharp in the same process.

We however absolutely do not want to allow multiple libgit2sharp versions to be loaded into the same process. That involves loading multiple versions of libgit2 into the process, which is either going to crash upon load, or (if the OS allows the functions in C's flat namespace to be overridden) it's going to crash because the structs aren't the right size or the functions have different signatures.

davidebbo commented 9 years ago

@carlosmn Please see @JamesNK's comment above. The idea is to never change the assembly version. So it would not allow loading multiple versions, which as you point out is a non-goal anyway.

The only goal is allowing SN assemblies to depend on it.

carlosmn commented 9 years ago

But now you're talking about working around the SN features and changing how the release works.

If we can't bump the assembly version, is there a way for the tools to keep track of which is the right version to load? The API hasn't stabilised. If a method gets extra parameters or gets removed, what do you use to know you're in the right version?

Not loading multiple versions isn't just a non-goal, it's something that must be avoided.

JamesNK commented 9 years ago

Strong naming doesn't mean multi versions can be loaded (as far as I know)

When I wrote what I said previously I wasn't aware that when you're strong named and there is a version in the GAC it will always take prescidence over what is in the bin. Something to keep in mind.

davidebbo commented 9 years ago

Yes, if it's in the GAC with the same assembly version, the GAC always wins. So clearly, for this scheme to work, you want to keep it out of the GAC, which is normally what happens in the NuGet world.

Note that if you have multiple versions in the GAC, you can in fact load them both, but it's rarely a good thing.

AArnott commented 9 years ago

I wonder why you keep the same assembly version across releases when your API hasn't stabilized. Wouldn't you want to rev the version so folks know there are changes and encourage them to recompile to accommodate the breaking changes?

carlosmn commented 9 years ago

Each release does bump the version to indicate a change in the API. It's the SN proposal which has libgit2sharp fixing its assembly version.

AArnott commented 9 years ago

@carlosmn Would that be to try to prevent apps from loading the assembly twice? You might be losing more than you're winning there. You can't prevent an app from loading your assembly twice, even by keeping the strong name the same. They can use two app domains, for example. And before adopting strong name signing, users can load your assembly twice already either with app domains or (I think) something far simpler: Assembly.LoadFrom, which allows you to load an assembly of a specific version, even if it's already loaded as a different version (as I understand it).

I think it would be a misuse of assembly versions to try to use it to prevent multiple loads at once. I think documenting that folks shouldn't do it is sufficient deterrent, without getting in the way of what apps do need to do, and creating other real technical problems by keeping your version the same between releases.

whoisj commented 9 years ago

@aarnott and that is one of the many reasons the CLR is abandoning the concept of AppDomains. They are a kludge for a problem that never really existed. There's really no good reason for this library to be strong name signed other to show loading into strong name signed execution space. Since libraries can be signed after the fact, I recommend the consumer sign as needed.

AArnott commented 9 years ago

@whoisj The consumer signing as needed doesn't work very well (if at all) in a nuget world, IMO.

Scenario 1: consumption I just want to install a nuget package, that I can regularly update to get the latest goodness from libgit2sharp.

Scenario 2: libgit2sharp as an indirect dependency I want to use libgit2sharp as well as another nuget package that builds on libgit2sharp to add functionality. The added value package depends on libgit2sharp. I cannot simply sign libgit2sharp and expect the other package to use it. In fact I have to sign the other package's assembly as well since it wasn't allowed to strong name sign since of its dependencies are not strong name signed. So now I have a chain of packages I have to sign, and keep signed as updates from them come. I have to come up with my own nuget feed or keep publishing my own packages to nuget.org that are variants of official ones that have no change other than being strong-name signed. And it's in my interest to try to get other folks writing packages that build on libgit2sharp to use my variant instead of the official one so that they can be strong-name signed.

In a world where the consumer is checking in binaries, and using nuget only as a library of DLLs that they download and then check in, yes, signing is not that out-of-the-way. But for the more modern developer (quite likely open source) who relies on nuget packages to avoid checking in binaries for their dependencies and keep them updated, packages with assemblies that are not strong-name signed make them very expensive to adopt and maintain a dependency on. When you contrast this pain to the relatively cheap cost (almost free) of starting to produce strong name signed assemblies in the original project packages, and the likelihood that more folks can now benefit from your work, it seems like a no-brainer to simply start strong-name signing in the original project.

ethomson commented 9 years ago

But for the more modern developer (quite likely open source) ... packages with assemblies that are not strong-name signed make them very expensive to adopt and maintain a dependency on.

Your assertion is that these developers are strong name signing their packages. Do you have evidence that this is common? My experience is that it is not. I don't think we should be optimizing for the strong name signed consumer if they're merely 1% of the users.

In fact I have to sign the other package's assembly as well since it wasn't allowed to strong name sign since of its dependencies are not strong name signed. So now I have a chain of packages I have to sign, and keep signed as updates from them come.

I'm not sure why you later contend that this is "almost free". It doesn't sound free, it sounds like a massive pain in the ass, and I don't know why we should inflict this upon ourselves. (Just because we don't have any not strong named dependencies today doesn't mean we won't tomorrow and cutting ourselves off from this opportunity seems like poor future planning.)

I suspect that you're saying something along the lines of: it would be almost free if all the other dependencies were also strong name signed, but they aren't (as we know). So who are we enabling here to take this risk? Can you provide this data?

AArnott commented 9 years ago

The only pain I've felt from strong-name signing is when I want to take a dependency on something that isn't strong-name signed. And yes, that's a real pain. And the pain can only all go away when everyone that produces libraries worthy of use are all strong-name signed. So yes, if you strong name sign libgit2sharp, we're pushing the problem upstream. At the moment, you're at the head of the stream so the job is done on this branch, but as you say, if you choose to take a dependency on another package, you'll have to just hope that they're strong-name signed because if they aren't, you'll be making all the same arguments and pleas that I'm making to you now, since right now I'm the one downstream from you.

So why should you do it? Because some folks must strong-name sign, but no one to my knowledge must not be strong-name signed. And your library is only available to those who are not in the must be signed camp. So you can broaden your tent and appeal to more consumers by strong-name signing. And contribute to enabling all your consumers to respectively strong-name sign their components so that they are also applicable to more consumers.

It's a very good point you raise though, and thus begs the question: How likely are you, really, to add a nuget package dependency to your library that isn't already strong-name signed?

olivier-spinelli commented 9 years ago

Totally agree with you @AArnott. I'm currently stuck on this. I, like a lot of other "Open Sourcers" sign my assemblies with a SharedKey.snk, and as its name states, it is public and available in all my repositories: no trouble to recompile the OS solution but when I release an "official and non pre-release" package (ie. to nuget.org), I sign them with my private shared key. It works fine: pre release packages or packages compiled by others are "publicly signed". Release and Official are "privately signed'. I feel comfortable with this... until I met LigGit2Sharp.

The only workaround I see is to produce a "Signed.LibGit2Sharp.nupkg" with a strong name. Should I then push it on nuget.org? What if everybody does the same? It's stupid!

Please guys, sign it! Even with a SharedKey.snk (take mine if you want) this will not be worse than now (no more security but more compatibility)... And if you can use a private one for official releases, it will be perfect.

deadlydog commented 9 years ago

I agree with @AArnott and @olivier-spinelli, and I quite like @olivier-spinelli's approach.

I almost wish that VS would sign packages automatically (even just with a blank password), or that NuGet.org would require packages to be signed. If everyone signed their packages, then we wouldn't have this problem.

ethomson commented 9 years ago

Out of curiosity, @olivier-spinelli and @deadlydog - what led you to start strong name signing your packages?

deadlydog commented 9 years ago

For me, it is required by Visual Studio for the VS extensions I create.

davidebbo commented 9 years ago

See https://github.com/davidebbo/WebActivator/wiki/Signing-WebActivator for what caused me to strong name WebActivator (with a fixed version). Before I did it, I was getting tons of complains. After doing it, not a single complain, and it's been 2.5 years!

Point is that doing this really doesn't hurt people who hate strong naming, and it does help people who need it. So it's mostly a no-brainer.

AArnott commented 9 years ago

Thanks for the comments. Truly, not to distract from this thread, but I don't so much care for @olivier-spinelli's approach of reserving a secret private key for signing official releases. I used to do that, but that does seem to violate the open source idea of "I can build your code myself and replace your binary" because if you're using a key I don't have, then I can't replace your binary as others that reference it will see my build as entirely incompatible.

But I don't mean to debate that on this thread. The reason I bring it up here is to suggest that for libgit2sharp to maintain your desire that anyone can build it, you should simply have the checked in, unprotected key pair in your git repo so that you, and everyone else, can build it and use their private build to replace the officially published one, just as they do today.

olivier-spinelli commented 9 years ago

@ethomson: nearly the same as for @deadlydog, it was a set of office plugins that had to use some shared, reusable libs. @davidebbo: funny, I remember now having followed this story some years ago! @AArnott: I choose this after some thoughts... I wanted a simple way to be able to say "Hey! this is not my code!".

AArnott commented 9 years ago

@olivier-spinelli I know. I had the same reason for keeping the private key a secret for my DotNetOpenAuth project. But it turns out, that's what Authenticode is for.

olivier-spinelli commented 9 years ago

@AArnott Yes but I liked the idea that it adds a strong (!) barrier between the released and pre released component (and nothing by design in .Net checks the Authenticode and this annoys me)... However, I'm considering changing this to go the same way as you.

Anyway, this has not a lot to do with our current concern... Should we create once for all a LibGit2Sharp.Strong repository and release a LibGit2Sharp.Strong.nupkg on NuGet.org as soon as a new release pops up ?

nulltoken commented 9 years ago

Hey all! Sorry for the late comment, but I was on vacations during the past weeks. Please, give me a few days to read and think about the content added to this thread.

Cheers!

shana commented 9 years ago

Just fyi, I'm in the same spot of having to sign assemblies due to having to ship GitHub for Visual Studio signed. This forces me to have to fork every single one of my dependencies so I can sign them, which makes CI a pain, building and testing a pain, upgrading a pain, and everything generally sucky. Sometimes you really don't have a choice about these things, no matter what your views are on signing :cry:

If nuget library packages would routinely ship signed and unsigned versions, this would be soooo amazing and would make my life so much easier.

nulltoken commented 9 years ago

@shana Just for my understanding, provided we took the decision to go the strongly-sign path, what would be the point of releasing both signed and unsigned NuGet packages?

carlosmn commented 9 years ago

I'm working on providing a managed SSH implementation for libgit2sharp, but the new dependency isn't signed. If we always sign libgit2sharp, we cannot make use of this dependency, or we'll have to go and annoy them enough for them to sign it.

shana commented 9 years ago

@nulltoken No real point, technically. Once a library is signed, it can be consumed by signed and unsigned apps and libraries, so there's no real need for an unsigned variant from a consumer's point of view. I just added both in my comment because I know that there's feels about this that go beyond the strictly technical arguments.

In scenarios where an app loads plugins, dependency loading is seriously broken when assemblies are not signed. Versions are not enough to distinguish assemblies from each other, and the only way to make sure that the libgit2sharp assembly that I want loaded is the one that actually gets loaded is for everything to be signed. This is the reality of .NET, and there's no way around it, unfortunately.

whoisj commented 9 years ago

This is the reality of .NET, and there's no way around it, unfortunately.

Is this not the reality of any kind of dependency loading?

I'm working on providing a managed SSH implementation for libgit2sharp, but the new dependency isn't signed. If we always sign libgit2sharp, we cannot make use of this dependency, or we'll have to go and annoy them enough for them to sign it.

^^ this is my primary concern about signing LG2#. We're going to end up with two variants: one signed but limited and one unsigned and complete. As I understand it, the assembly can be signed after the fact and therefore I'm confused as to why the consumer cannot sign the assembly they want after we publish?

shana commented 9 years ago

Is this not the reality of any kind of dependency loading?

Not really. In other dependency loading/tracking systems, name+version is the key to identify which dependency to load (the best example of which is the debian packager, but msi installers is another). In .NET, it's name+version+token.

^^ this is my primary concern about signing LG2#. We're going to end up with two variants: one signed but limited and one unsigned and complete. As I understand it, the assembly can be signed after the fact and therefore I'm confused as to why the consumer cannot sign the assembly they want after we publish?

Assemblies cannot be signed after the fact and still be useful without a key. When delay-signing is activated, .NET will refuse to load the assembly until the signing is complete. Delay-signing serves a different purpose, which is mostly pretend-security - i.e., using signing as a way to ensure that what you're loading has not been tampered with. Developers sign with temporary development keys, and when you release, you replace things with the permanent key. Unfortunately, this is all make-believe, because the runtime doesn't validate that the hashes encoded in the assemblies match the key (that would cost too many cycles when loading the class libraries and make .net slower in general), so signed assemblies can be rewritten and keys regenerated after the fact. All in all, delay signing is not an option (did I mention the whole .NET versioning/signing architecture is #$#$%$%? Again, it's the reality we have to live with)

In the case of the SSH dependency mentioned above, that SSH library faces the exact same problem libgit2sharp faces: if it's not signed, anyone that requires signing due to how .NET versioning and dependency tracking is implemented will be forced to fork and sign it; if it's signed, everyone can use it.

olivier-spinelli commented 9 years ago

Just to say that, since I can not depend on any official LibGit2Sharp dll (because I am signed), my current workaround is to locally sign it and ILMerge it to my dlls. One of this merge is mandatory (MSBuild task library), but the other one (a Cake build extension) is just stupid.. but it works.

thecodejunkie commented 9 years ago

Up to this point, nine days and lots of time have been spent discussing this. That's only the time spend trying to convince LibGit2Sharp to add a strong-name - the same amount of time would have to be spent trying to convince all other non-signed dependencies you have to sign. That's a lot of wasted time. Time that could have been spend doing other things (one suggestions follows shortly). All this because, in it's essence, this is a poison that is being caused because somewhere along the tech stack (be it VS to allow you to author plugins or what not) that you use, someone decided to involve a strong name, this forcing the rest of the stack to follow suite (or end up with this discussion over and over again - this must be my 10th time).

I'm going to go out on a limb and say that for a vast (like realllly vast) majority this only comes down to "I'm using X that is signed so I need Y and Z" to also be signed" and nothing to do with security or to authenticate the origin of the dependencies

Here is my suggestion on what to do instead

sign them yourself!

Really. Hear me out. You don't need my key.. you need A key .. that's a huge difference. Don't burden me with your problems (really). Hard to do? No, but it sure if going to be a bit tedious to do over and over again (and in reality, how often does you dependencies bump their version? And do you really have to upgrade those dependencies to the latest version all the time? Could it be done in a more controlled fashion?) so why not automate it?

Or make it easy for yourself

Seriously you can run a Team City instance on pretty much any box and you can even host it on Azure for peanuts (if you have MSDN you might even have free monthly azure credits to spend). The point is, this is not hard and would

  1. Give you more control
  2. Save you time from walking your dependency chain and trying to convince the author(s) to see your way on the subject

We (NancyFx) must have gotten this request 20+ times over the years and and we've put a Statement on Strong Naming on our Wiki - In short, we'll not sign it. 20 might sounds much, but we've had 100,000's of downloads so it's a drop in the ocean.

Newtonsoft.Json - probably the most famous strong named open-source project has caused more devs assembly-binding-rediects nightmares than anything else know to the .new community even though they've taken the "lock the file version and bump the assembly version" (or the other way around, can't really remember right now) approach ... It's not going to be as easy as "if you sign it you help everyone, but if you don't then you screw over the guys that needs it to be signed"

To the LibGit2Sharp-team... I urge you to gain some perspective of this.. how much work will it cause you down the road (evaluate it!).. how big portion of your user base actually require YOU to publish a strong-named version.. add it up and see if you find it a viable option. Then, and only then, move forward with this.

olivier-spinelli commented 9 years ago

Sorry, I prefer assembly rebindings issues to missing method exception runtime errors. But I should be an old school guy...

thecodejunkie commented 9 years ago

@olivier-spinelli Cool. Automate the signing of your dependencies https://www.jetbrains.com/teamcity/ and pull the nugets straight from it. Problem solved. No?

davepermen commented 9 years ago

i'm with @thecodejunkie don't demand libs to be signed. do it yourself if you really need to (and then, i feel sorry for you), but don't demand it on the rest of the world. that just spreads that dissease.

olivier-spinelli commented 9 years ago

I just want my OS packages to be on nuget.org ecosystem not on yet-another-nuget-source. And don't want to pollute it with my own signed ones.

thecodejunkie commented 9 years ago

@olivier-spinelli

I just want my OS packages to be on nuget.org ecosystem not on yet-another-nuget-source.

Nuget was never designed to be a single-repository design. There's even native support, for multiple repositories, in the visual studio package manager

And don't want to pollute it with my own signed ones.

I'm sorry but this is just another way of saying "I don't want the burden, better someone else has it". Having the LibGit2Sharp team sign it adds zero benefits, other than you not having to do it yourself. No heightened security nor trust. There'd, literally, be no binary difference between what you build and sign, and what someone else builds and signs, because you'd be building straight from the source code that you pull in from the repository

shana commented 9 years ago

that just spreads that dissease.

Aaaaand this is why I am weary to get into these discussions and I say there's feels. Signing is not a disease, it's a basic requirement of versioning in .NET, and while I understand how annoying it is (because yes, it is stupid and the GAC is annoying and assembly redirects are annoying and the whole thing is badly thought out), it is what it is. It's not a disease or a curse or a conspiracy or evil or whatever, it's just a versioning architecture.

sigh

AArnott commented 9 years ago

@shana No one (AFAIK) is recommending that any open source project use delay signing. Certainly not on this thread. The "private key" should be checked in unprotected so anyone can build the project as they can today.

Shana says:

This forces me to have to fork every single one of my dependencies so I can sign them, which makes CI a pain, building and testing a pain, upgrading a pain, and everything generally sucky.

This is why it's not cool to just tell folks to sign packages themselves. It's not shifting the burden -- it's eliminating it. Its very hard to sign the packages ourselves as consumers. But it's trivially easy to sign the original.

Also: while for this particular package, it's unlikely to be used for interop across assemblies. But for many other very useful packages on NuGet (we'll call one package X), it's very important that an application that uses several packages be able to share a common definition of X and talk to each other with types defined in X. That's impossible if all these other packages have their own copies of X, signed with their own keys. Flat out impossible. No buts about it. The only way to fix this is for X to sign itself and thus everyone shares a public key for it.

Since that's the undebatable truth for many packages, by libgit2sharp signing itself, it becomes useful to package X.