And after another long hiatus... Somebody royally forked up!


Did you ever had to deal with one of those small, annoying changes that broke some code unexpectedly, and yet weren't announced anywhere?

This is such a case. (overdramatized sensationalistic blog post ahead)

The Bad Good Place

Let me provide a little context for the situation.

I had just updated my production server to the shiny new Ruby 2.5 Xmas version, performance boosts blazing through my bits & bytes, a sense of satisfaction slowly revolving around my ego. Everything is fine.


Suddenly, a 3rd party payment entity raised an issue with some newly self-signed certificate that is generated dynamically by one of my methods, untouched for more than 1 year. I see the certificate was sent by a customer, and not by someone in our team.

"Fork me, he probably made some mistake", I thought to myself, and gently proceeded to blame the customer and sent a new certificate myself (which unfortunately still happens on occasion).

A reply quickly followed, the new certificate's information was also invalid, said the 3rd party. "Bullshirt!" I said, and gently proceeded to blame the 3rd party, ranting that they must have changed something in their own processes.

A 2nd reply arrived even quicker, the new certificate's information laid before me on screenshots of a Windows' "Properties" window (Windows' "Properties" window, Windows' "Properties" window....), unformatted, incorrect. I was speechless.

Since I had been such a nice guy before, they also verified the other latest certificates we had sent them since the server update, and promptly sent me proof that they were also all wrong.

What the fork is happening?


The Bad Place

Debugging and searching took me to the real bad place, Ruby's source code. Without further ado, let me present you with the faulty file:

ruby/ext/openssl/lib/openssl/x509.rb

And here is our beloved prize winner of a method:

BEFORE:

https://github.com/ruby/ruby/blame/b9801bb8b2f5ce91755e0076d79140a72ff94d6a/ext/openssl/lib/openssl/x509.rb#L141

def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE)
  ary = str.scan(/\s*([^\/,]+)\s*/).collect{|i| i[0].split("=", 2) }
  self.new(ary, template)
end

AFTER:

https://github.com/ruby/ruby/blame/trunk/ext/openssl/lib/openssl/x509.rb#L146

def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE)
  if str.start_with?("/")
    # /A=B/C=D format
    ary = str[1..-1].split("/").map { |i| i.split("=", 2) }
  else
    # Comma-separated
    ary = str.split(",").map { |i| i.strip.split("=", 2) }
  end
  self.new(ary, template)
end

Spotted the difference yet? Allow me to make it a little more clear to you:

BEFORE:

ca = OpenSSL::X509::Name.parse("C=PT/ST=Some-State/L=PORTO/O=BinaryLies/CN=123456789")

AFTER:

ca = OpenSSL::X509::Name.parse("/C=PT/ST=Some-State/L=PORTO/O=BinaryLies/CN=123456789")

Holy motherforking shirtballs!

One single mandatory "/" at the beginning of the certificate information changes everything. And voilá, just like that, my self-signed certificates were all valid again. Un-forking-believable.

By now you're probably wondering if I missed something, however, I did my homework and reviewed the release notes and related details:

Not even one mention of it.

The Real Good Place

Finally, I had to pull myself down to earth. I pushed the fix online and looked at the email thread with the customer and the 3rd party again.

My morality score was certainly negative at this point, so I had to remain humble, recognize my mistake and apologize.

Pobody's nerfect!