Summarized using AI

The Challenges of Building sigstore-ruby

Samuel Giddins • April 18, 2025 • Matsuyama, Ehime, Japan • Talk

The talk "The Challenges of Building sigstore-ruby" by Samuel Giddins at RubyKaigi 2025 explores the complexities and hurdles encountered while creating a pure Ruby implementation of the Sigstore client. Sigstore is designed to enhance the integrity of software supply chains through verifiable and securely signed packages. Giddins discusses the primary goals of building Sigstore Ruby, which include creating a client that strictly utilizes Ruby's standard library and ensuring seamless integration with Ruby Gems and Bundler.

Key Points Discussed:
- Purpose of Sigstore: Sigstore's main function is to establish the provenance of software packages, creating a system that verifies where a gem was created, from which commit, and by which CI system, all in a cryptographically verifiable manner.
- Implementation Goals: The goals focused on a pure Ruby solution that is fully vendorable within Ruby distributions, avoiding the need for external dependencies.

- Main Challenges: Giddins details the complexities faced, such as:
- Navigating the intricacies of the TUF (The Update Framework) protocol,
- Implementing custom X.509 handling,
- Supporting multiple cryptographic signing algorithms, and
- Abstracting over different key types.
- Technical Features Required: The implementation requires addressing various cryptographic primitives, integrating with multiple services like transparency logs and certificate issuers, and implementing a variety of standards and protocols (e.g., JSON canonicalization, handling of timestamps, and more).
- Progress Made: Despite starting this project in February and deploying it in October, Giddins highlights the lessons learned about the extensive plumbing and multiple layers of functionality that underpin the two core functions of the Sigstore client.
- Community Aspect: The evolution of Sigstore from a prototype to a widely adopted system is emphasized, encouraging future contributors to simplify the process of building Sigstore clients.

In conclusion, Giddins intends for his experiences building Sigstore Ruby not only to illuminate the challenges involved in such software development but also to inspire a community-driven effort toward creating more straightforward implementations in the future. He invites attendees to connect with him about contributions or discussions related to Sigstore Ruby, fostering collaboration and growth in this critical area of software security.

The Challenges of Building sigstore-ruby
Samuel Giddins • Matsuyama, Ehime, Japan • Talk

Date: April 18, 2025
Published: May 27, 2025
Announced: unknown

Sigstore Ruby now exists. So exciting! But bringing it to life was a challenge, particularly due to the goal of being able to ship it as a part of Ruby itself. Building a sigstore implementation atop only the standard library required writing a TUF client, implementing custom x509 handling, and abstracting over all the supported key types, among other challenges. This talk will explore those challenges, and dive into _why_ a sigstore implementation proves to be such an undertaking, hopefully inspiring some simplification for the next poor soul who attempts to build one from scratch.

https://rubykaigi.org/2025/presentations/segiddins.html

RubyKaigi 2025

00:00:08.160 well I know that everyone is probably at this point just dying to hear Matt's
00:00:15.360 keynote and take a long long nap Um it's
00:00:20.560 been a fun week So thank you for showing up for what
00:00:27.119 might be the most boring talk of this entire conference Going to talk about
00:00:32.320 Sig Store Ruby Um just a little show of hands Who here
00:00:39.200 has heard of Sig Store i see a couple of hands Who here
00:00:45.520 has heard of Ruby
00:00:50.800 well you're at Ruby Kaiiki Come on You've heard of Ruby Um so uh one of the two parts of this
00:00:58.559 talk will be a bit more familiar than the other So a little bit about me I am at
00:01:07.439 SE Giddens everywhere on the internet Um my name is Samuel I am the Rubygeems
00:01:15.680 Bundler and Rubygeems.org or uh security lead and a member of those core teams
00:01:22.280 Um by day I am the security engineer and residents at Ruby
00:01:28.280 Central and also by day cuz you know part of my job um I am the developer
00:01:35.200 maintainer and most frequently the debugger of sig store Ruby
00:01:43.360 I just want to say uh if there's anyone else observing Passover this week I did
00:01:50.320 have to bring my own supply of matzah to Japan Um they took up about half of my
00:01:59.079 suitcase Okay now on to the meat of this talk Now as a
00:02:06.520 disclaimer everything in this talk is about the learnings I took away from building Sig Store Ruby In no way do I
00:02:14.239 mean this to be a negative talk Um I know I sometimes sound negative It's not
00:02:21.480 intentional It's just you know being a New Yorker Uh but I want us as a
00:02:27.040 community to take away inspiration on how we can make building this sort of software in Ruby just a little bit
00:02:35.560 easier So Sig Store Ruby is a pure Ruby implementation of SIG store verification
00:02:42.800 and signing There's a lovely GitHub repo that I'm amazed that 18 people have
00:02:49.920 bothered to star Uh it is also a Ruby gem of course
00:02:56.400 the Sig store Ruby gem Um and what's
00:03:01.680 pretty cool about Sig Store Ruby is it powers that very bottom section of the
00:03:09.840 gem page on rubygeems.org That little bit that says Provenence built and signed on GitHub actions the commit the
00:03:18.159 build file the transparency log entry Yeah that's the thing that I spent like
00:03:25.519 all of last year building um just to get that one little bit on this
00:03:31.959 website So SIG store for those of you
00:03:37.040 who aren't familiar it's built to answer the question of where does a gem come
00:03:42.680 from what what commit was it built from what repo when was it built on what CI
00:03:50.080 system and to answer that question in a way that is cryptographically
00:03:55.959 verifiable and in the future all this is going to be used as the basis for
00:04:01.120 policies around what gems to install So you can say things like
00:04:06.439 hm every gem that starts with the prefix
00:04:11.480 AWS where should that come from who should have published it
00:04:16.680 probably the folks over at AWS because you know they've got like 230 of them
00:04:22.479 and sometimes it's hard to keep them straight So what is a sig store client
00:04:30.639 what is it I spent all this time building it can be summarized as two
00:04:36.160 functions Function number one verifies that an
00:04:41.199 artifact and a SIG store bundle match against a trusted route And function
00:04:48.000 number two is cryptographically signing an artifact against a trusted route
00:04:54.160 Piece of cake Two functions No challenges here Okay fun talk I'll see you later
00:05:04.759 Unfortunately those two functions are
00:05:09.800 complicated So getting back into it take a step back What is a SIG
00:05:16.360 store i will quote from their website uh sigstore.dev Software safety
00:05:25.000 integrated a combination of technologies to handle signing verification and
00:05:30.560 provenence checks that respect privacy and work at
00:05:36.120 scale Show of hands if that means anything to
00:05:41.479 you No Okay So we've we've all been uh sort of inoculated against marketing
00:05:47.280 speak Good Good to see
00:05:53.120 Being a bit more specific what is a sig store it's um composed of a bunk a bunch
00:05:58.880 of things Number one is a a uh tough which stands for the update framework
00:06:06.000 It's a tough repository that serves a trusted route Um it is a service that
00:06:12.639 issues signing certificates based on OIDC tokens
00:06:18.800 Don't forget there's also a certificate transparency log so you can see what certificates have been
00:06:25.000 issued There's a service that records stuff in a transparency log and there's
00:06:33.120 a bundle format that has artifacts from all of the above that you can show to
00:06:38.880 people and they can do the cryptographic verification
00:06:44.919 thing Above all you know six door is a crypto system that we're going to use to
00:06:50.319 build trust for software artifacts Super serious stuff So it's important we get
00:06:55.680 it right Now I went and I asked um our favorite professor Professor Chachi BT
00:07:04.319 why is writing a SIG store client from scratch so
00:07:09.479 difficult here's the answer Writing a SIG store client from scratch can be
00:07:14.560 quite difficult for several reasons Primarily due to the complexity of the underlying cryptographic protocols
00:07:21.360 integration with various services and handling security concerns Here's a breakdown of the challenges you might
00:07:28.120 face Uh I I love just getting other people to write my slides for me There's
00:07:34.240 the complexity of the SIG store protocol There's the sheer volume of the
00:07:39.840 cryptographic foundations There's interfacing with all the different services all the security configurations
00:07:46.960 I love how that's just a bullet point You know we're we're building a security library One of the things that one of
00:07:53.360 the challenges is the security that real helpful integration with other
00:07:59.400 tools API design and usability testing and debugging So basically everything
00:08:05.599 about building a SIG store client from scratch is difficult Thanks Chat GPT makes me feel
00:08:13.080 better We had a bunch of implementation goals when I set off to build sig store
00:08:18.520 Ruby Number one was having a pure Ruby implementation of both signing and verification flows
00:08:26.680 Um number two was being 100% vendorable inside of Ruby Gems and
00:08:32.680 Bundler because we want to be able to be to vendor Ruby gems and bundler inside of
00:08:40.479 the Ruby distributions So anything vendored inside of Ruby gems and bundler needs to also be vendorable
00:08:48.160 And um biggest implementation goal was don't trust this guy to write any novel
00:08:54.640 crypto cryptography code Here's a kind of difficult question
00:09:02.640 What does it mean to be pure Ruby i came up with a with a couple of
00:09:10.160 options here Um just let me know when you think I've I've gotten to a good definition Well
00:09:17.519 there's an ISO standard for Ruby So is pure Ruby something that runs on ISO
00:09:24.920 Ruby probably not I don't think I've seen anyone reference that standard in
00:09:30.000 quite a long time Is it only Ruby code that's
00:09:35.680 runnable with mini Ruby is it Ruby code that only requires the
00:09:43.040 standard library or only requires the standard library and bundle
00:09:49.480 gems and you know what about J Ruby and truffle Ruby running Ruby on Wom
00:09:57.080 etc All these are kind of reasonable definitions for what it means to be pure
00:10:02.640 Ruby code And the consensus around theu community
00:10:09.120 seems to be uh you know it's the non-endoflife versions of MRI and the
00:10:17.040 latest version of J Ruby and the latest version of truffle Ruby and using the
00:10:22.320 stuff that's available on all three of them which is the standard library default and bundle gems Um this is what
00:10:31.680 Ruby gems targets This is what bundler targets This is what a bunch of the Ruby
00:10:37.519 gemified standard library targets But writing pure Ruby is still
00:10:45.600 kind of hard You know industry standard
00:10:50.800 libraries for better or more realistically for worse are still mostly
00:10:56.480 written in C They're not written in Ruby
00:11:01.839 Ruby standard library primitives are largely implemented in native code not
00:11:07.519 in Ruby And every layer of wrappers that we
00:11:13.360 deal with leads to sort of an increased impedance mismatch
00:11:20.320 You know trying to write code that digs through like six layers of
00:11:26.000 wrapper code means it's hard to sometimes do exactly what you need to
00:11:32.920 do And finally when you're supporting older Ruby versions and multiple
00:11:40.079 different implementations of Ruby well that means you can take even less for granted you you can only depend on
00:11:46.079 what's in the intersection of all of those different
00:11:51.720 Rubies So it was suggested for a while that
00:11:56.959 everyone building a SIG store client should just write a wrapper around some Rust libraries That was going to be the
00:12:03.959 solution Like Rust fixes everything I was told
00:12:09.760 But you know it turns out that wasn't going to work for us Having a dependency on compiling
00:12:15.600 native code was a no-go for something that needed to be able to ship inside of Ruby gems
00:12:22.760 Um also like writing Rust code that then deals with both Java and C sounds like a
00:12:32.560 miserable time We also want the ability to you know
00:12:39.200 update SIG store Ruby outside of Ruby releases So we weren't going to go and
00:12:44.240 stick that Rust wrapper inside of Ruby itself Um not that they would let me do
00:12:50.160 that anyway And as I said before you know support
00:12:55.279 for all of the different platforms that Ruby runs on makes wrapping a single native
00:13:01.839 library a really hard answer And unfortunately all that you
00:13:08.959 know just rewrite it and rust and everything will be fixed and the world will be perfect um turned out just to
00:13:16.800 not be the panacea we were promised So I started on our pure Ruby
00:13:24.399 implementation of a sixstar client wrote the first line of code last
00:13:30.680 February shipped the first release in October and had its first use in
00:13:37.200 production uh mid November of last
00:13:43.720 year That's a lot of time I spent building this one small library
00:13:51.760 So you know Sam why are you so bad at your job why' this take you so
00:13:58.040 long i heard you all agreeing with that comment Um and it turns out that doing
00:14:04.880 an endto-end store verification flow it does a lot of stuff So I'm just going to
00:14:10.639 roll through the list of all the things here And if you're confused about any of the
00:14:17.480 acronyms well yeah there's just a bunch of acronym soup to learn So SIG store
00:14:25.120 deals with the update framework which is a whole specification unto
00:14:31.800 itself Bunch of different functionality like refreshing tough repositories
00:14:37.360 downloading targets We then uh read a SIG store bundle JSON
00:14:44.320 file and validate the bundle We have to be able to cryptographically hash artifacts to match them against the
00:14:51.320 bundle Establish a trusted source of time from the bundle and X509
00:14:58.560 certificates perform X509 path validation to make sure that the certificates we're
00:15:05.959 verifying actually come from the root of trust that we're
00:15:11.399 expecting Perform signed certificate timestamp validation against those
00:15:17.800 certificates Verify inclusion of log entries in transparency log Verify the
00:15:24.800 certificate against a policy that says "Hey what who's supposed to be able to
00:15:30.160 sign for this artifact?" Verify that the signatures against the artifact and the the signing
00:15:36.959 certificate match and ensure consistency between
00:15:42.720 what's in the payload of the bundle and the policy for the artifacts that we accept and the signing certificate and
00:15:49.759 make sure all that matches And you know cross your fingers and hope you did all
00:15:56.160 1 2 3 4 5 6 7 8 9 10 11 12 13 steps
00:16:02.839 correctly And you know I'm just a Ruby engineer Um it's a lot of plumbing that
00:16:10.880 I was responsible for um in addition to needing to write a lot of these
00:16:16.199 primitives And unfortunately the guarantee that all of these pieces work
00:16:22.040 together is the responsibility of the person implementing a SIG store client
00:16:29.240 And that's me and that's that's kind of scary I don't you know I don't trust
00:16:38.120 me So there are a bunch of primitives that
00:16:44.079 are needed to even start thinking about implementing a sigar client and they're
00:16:49.680 all things that you probably take for granted but it's worth enumerating them
00:16:55.880 So sig store stuff is defined in terms of Google's protocol buffers
00:17:03.519 Fortunately only the JSON encoding of protobuffs is used But that means you
00:17:09.520 need a protobuff implementation You need support for
00:17:14.559 different cryptographic signing algorithms Um just what's required by the spec
00:17:21.360 specification is RSA uh elliptic curve DSA and ED25519
00:17:29.440 You have to support X509 signing certificates RFC 3161
00:17:36.160 uh signed certificate timestamps I'm pretty sure that RFC is signed certificate timestamps If anyone knows
00:17:42.400 off the top of their head that I am wrong congratulations I'm sorry you know that off the top of your head Um there's
00:17:50.080 this signed note format that Go started using for uh the Go Sum DB There's
00:17:57.600 Merkel trees underpinning all of the public transparency log stuff There's
00:18:03.520 two different JSON canonicalization formats because did you know that there
00:18:08.799 exists two different JSON canonicalization formats that's like two times as many as there should
00:18:16.600 be And then there's even more primitives that are needed to sign So there's all
00:18:22.960 of the stuff from the previous slide In addition you have to be able to understand
00:18:29.120 uh JavaScript web tokens that represent an open ID connect
00:18:35.720 identity You have to generate a key do more X509
00:18:42.039 stuff create one of those uh signed
00:18:47.400 timestamps make a signature and then speak the log entry format that SIG store
00:18:56.559 expects which is a whole bunch of acronym soup Um and sometimes I wish I
00:19:02.720 didn't know it because it's not the tasty kind of soup you know And then there's HTTP clients that
00:19:11.200 are needed to talk to two of the different services that constitute SIG store Uh Recor is the transparency log
00:19:19.760 Folio is the uh certificate issuer
00:19:27.080 And as I was building all this I'm like that is a lot of primitives that you're
00:19:33.039 assuming that every language has available and documented and working
00:19:41.799 properly and implemented securely And in the places where that
00:19:47.600 stuff wasn't available or wasn't documented or wasn't working properly I had to go and implement stuff
00:19:55.360 myself And as I said I don't trust me to do that And also that that's a lot of work
00:20:02.799 that I didn't want to do So as a reminder everything I was doing was
00:20:10.480 limited to the Ruby standard library because the goal is to ship a SIG store verifier inside of Ruby
00:20:17.400 Gems and Ruby Gems ships with Ruby and J Ruby and
00:20:24.559 Truffle Ruby So that means no dependencies that can't be vendored
00:20:32.159 which means plain Ruby code So what do we have available we have the
00:20:38.000 OpenSSL gem That's about it for cryptographic primitives We have the JSON
00:20:45.880 module That was everything that the standard library gave me that I needed
00:20:52.039 So diving in OpenSSL gem it is a wrapper around OpenSSL It supports a bunch of
00:21:00.320 different OpenSSL versions Um up until last year I think it supported up to all
00:21:08.080 the way back to like OpenSSL0.9 something which is
00:21:14.360 ancient Um on some of those supported OpenSSL versions it didn't have support
00:21:19.520 for ED25519 which I needed to be able to pass the uh SIG store conformance test
00:21:26.960 suite it's missing some functionality that's present inside of
00:21:33.240 OpenSSL So things like querying about certificate properties
00:21:39.159 Um there's the fact that extension oids and short names get mixed up Um so being
00:21:47.200 confident about what X509 extensions you're talking about is a little
00:21:52.640 difficult um pulling out the to be signed
00:21:59.280 prescertificate bytes from a certificate wasn't implemented until I
00:22:04.320 sent a pull request and even then because we support all the way back to
00:22:10.480 Ruby 3.2 I had to reimplement that in pure Ruby as
00:22:16.600 well Was missing sign certificate timestamp validation which again had to
00:22:22.640 just reimplement in pure Ruby and was also missing RFC
00:22:28.760 3161 validation at my choice of time stamp it would only validate against the
00:22:36.000 current time which is not what Sig store needs Now believe it or not this is the
00:22:44.080 better version of the OpenSSL gem There's also J Ruby
00:22:50.600 OpenSSL And not a knock on the J Ruby team writing a Java based wrapper around
00:22:58.320 a Java library that tries to mimic the OpenSSL API is a challenge and I
00:23:05.760 wouldn't want to do that So kudos to them for doing it but it's missing even
00:23:10.960 more stuff So J Ruby OpenSSL is a bouncy castlebased implementation of the
00:23:17.679 OpenSSL API There is a longstanding bug where
00:23:23.120 you can't do X509 path validation against a certificate chain that has
00:23:29.039 intermediary certificate authorities It's missing ED25519 support
00:23:36.919 entirely It's missing or it was missing I I have to give them credit They they
00:23:42.720 did implement this a few months ago Uh public key dur export
00:23:48.280 support and it's also missing everything else that's missing from the C Ruby
00:23:54.200 gem Now it was so difficult to get some of the stuff working on J Ruby that I had
00:24:01.600 to reach down into the raw Java.security APIs to have support for all the primitives I needed So here's a here's a
00:24:09.360 fun commit that I landed last December where I reimplemented all the
00:24:15.360 missing J Ruby functionality just using I mean just in this room we were talking about how in J Ruby one of the great
00:24:23.360 things is you can just use Java classes and Java methods I had to do that to get
00:24:29.279 sig store Ruby working on J Ruby and uh it was a whole bunch of changes and if
00:24:36.640 you look at those changes they're pretty gnarly
00:24:44.120 So inside of SIG store Ruby itself we also implemented an X509
00:24:51.279 wrapper to allow querying certificate properties and typed extension
00:24:57.320 values Implemented RFC 8785 JSON
00:25:02.360 canonicalization As I said there's multiple JSON canonicalizations This is one of them
00:25:09.360 um implemented by
00:25:14.520 manually shifting around the uh ASN1 bytes of X509 certificates Um grabbing
00:25:22.559 out the to be signed prescertificate during coding Um everything with signed
00:25:28.960 certificate timestamps also had to be implemented outside of an SSL
00:25:35.159 um RFC 3161 support uh support for DESI
00:25:41.520 which is something simple dead simple security envelopes I
00:25:48.559 think is what DESI stand for not that dead simple I had to implement an entire
00:25:56.799 tough client um and you know it was tough to do that Um and inside of TU is
00:26:06.240 where I got to implement the second JSON canonicalization
00:26:12.279 scheme How did this all get so complicated we started by saying we're
00:26:17.679 implementing two functions This is a lot of stuff for two functions
00:26:24.480 And it happened because SIG store is the amalgamation of multiple different
00:26:30.520 systems and uses X509 for PKI TU for a trusted material
00:26:37.240 distribution Merkel trees for transparency log inclusion and signotes for
00:26:42.760 checkpoints All of these have libraries in some ecosystems If if you write go
00:26:48.480 you can you know import the X509 library You can import the tough client You can
00:26:54.640 import the Merkel tree library You can import the sign note library from
00:27:00.760 uh uh Go's module system All of that's there but not all of these things are
00:27:08.080 there for Ruby and they're certainly not all in the standard library And even X509 which is is difficult to work with
00:27:19.679 Additionally all these things have multiple different configuration points
00:27:25.440 different switches you can flip and that leads to a combinatorial explosion of
00:27:30.640 the configuration space Different key types signature schemes two ways of
00:27:36.559 encoding uh the contents of SIG store bundles different desi payload types
00:27:43.200 different in toto statement types That's just a lot of different stuff to
00:27:51.720 implement And you know the point of building all this wasn't even just to build the client It was to integrate it
00:27:59.600 into Ruby gems and rubygeems.org The good news is you know
00:28:06.480 this was not the first sig store client It was one of many And implement
00:28:11.960 re-implementing these things helps mitigate the impact of a bug found in any one
00:28:18.440 implementation Re-implementing this stuff over and over helps make the sig store specification more precise and
00:28:26.080 more portable You know each new implementation including sig store ruby
00:28:31.760 hit different edge cases You know each implementation provides a
00:28:37.200 different way of thinking about the specification we're trying to
00:28:42.679 implement Luckily everything I was doing got a little bit better in 2024 We got
00:28:49.360 multiple conformance test suites so I didn't have to go and write my own tests
00:28:54.640 There was a wonderfully edited SIG store client talk that I could go down and use as a checklist
00:29:01.600 There's convergence between different implementations There's a moxig store implementation and uh my buddy William
00:29:08.720 Woodruff just helped me a ton And so okay we talked a bit about
00:29:16.080 building SIG store clients from scratch The good news is the next client someone builds will be easier You know
00:29:24.320 the Sig store community has gone from prototyping to early adopters to now being sort of a critical and well-used
00:29:30.880 production system Building Sig Store Ruby last year
00:29:35.919 was challenging and I really hope that the next person building a SIG store
00:29:41.600 client has an easier time So come say hi if you want to talk
00:29:48.559 about or contribute to SIG store Ruby Come say hi if you want to make
00:29:54.640 implementing sig store Ruby a little bit easier in Ruby And thank you
Explore all talks recorded at RubyKaigi 2025
+66