Skip to content

Instantly share code, notes, and snippets.

@emboss
Last active August 29, 2015 14:00

Revisions

  1. emboss revised this gist May 1, 2014. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions analysis.rb
    Original file line number Diff line number Diff line change
    @@ -166,5 +166,5 @@

    spoof = OpenSSL::PKey::RSA.new(2048)
    cert = OpenSSL::X509::Certificate.new(File.read("some_certificate.cer"))
    root = cert.sign(spoof, OpenSSL::Digest::SHA1.new) # original certificate, resigned with new RSA key
    puts "New Root certificate verifies with the new key? #{root.verify(key)}" # true and expected
    resigned = cert.sign(spoof, OpenSSL::Digest::SHA1.new) # original certificate, resigned with new RSA key
    puts "Resigned certificate verifies with the new key? #{resigned.verify(key)}" # true and expected
  2. emboss revised this gist May 1, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion analysis.rb
    Original file line number Diff line number Diff line change
    @@ -15,7 +15,7 @@

    # The following lines are redundant. What this code does is it
    # creates a copy of the original certificate.
    # ef.issuer_certificate' is what's going to be signed below,
    # 'ef.issuer_certificate' is what's going to be signed below,
    # it would be equivalent to simply sign 'certificate' instead.
    # IMHO it doesn't matter if the certificate is changed or not
    # in any way for what is to follow.
  3. emboss revised this gist May 1, 2014. 1 changed file with 8 additions and 6 deletions.
    14 changes: 8 additions & 6 deletions analysis.rb
    Original file line number Diff line number Diff line change
    @@ -75,24 +75,26 @@
    # This prints 'true', but this is expected behavior. We resigned the certificate
    # with the new key, and all that X509Certificate#verify does is to check whether
    # a certificate's signature is valid given a certain private key. It *does not*
    # check whether the private key actually matches the public key which is part of
    # the certificate.
    # check whether the private key actually matches the public key information which
    # is part of the certificate itself.
    # While it certainly is surprising at first that X509Certificate#verify reports
    # "true" in this case, this is still correct behavior. Since we resigned the
    # certificate, it faithfully reports that the signature is correct. While the
    # signature value matches, it is true that the public and private key do not. But
    # signature value matches, it is true that the public key referenced in the
    # certificate and the private key used for issuing the new signature do not. But
    # this is not a security-critical issue IMO. A certificate resigned this way
    # cannot be used for anything meaningful unless you tricked somebody into explicitly
    # trusting it.
    # trusting it. But such an attack is clearly out of scope concerning the
    # responsibilities of Ruby OpenSSL as a library.
    # In fact, OpenSSL itself doesn't check whether keys match for root certificates.
    # We trust root certificates merely by possession (i.e. they are part of our
    # preconfigured 'trust stores').
    # Everybody could generate a valid self-signed root
    # certifcate, whose data may even match exactly that of a real CA root certificate.
    # But they are not included in any trust store (browser, OS, etc.) and thus useless
    # unless you explicitly/actively want to trust such a certificate.
    # unless you explicitly/actively *want to* trust such a certificate.
    # If we spoofed an intermediate or end entity certificate in this way, it wouldn't
    # help either because the signature would no longer be accepted during the PKIX
    # help either. Because the signature would no longer be accepted during the PKIX
    # validation process (where the signature of the spoofed certificate would be checked
    # against the public key of the supposed issuing certificate and consequently fail).
    puts root.verify(key)
  4. emboss revised this gist May 1, 2014. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion analysis.rb
    Original file line number Diff line number Diff line change
    @@ -133,7 +133,8 @@
    file.syswrite("#{cert.to_der}")
    printf "Check spoof.cer"

    # keys.rb (proving that reloading the "spoofed" key file loads the original key)
    # keys.rb: Proof that mixing the original certificate public key with a newly generated RSA
    # key in one file actually yields only the new RSA key when reloaded. Nothing shady happening.
    #####################################################################################

    require 'openssl'
  5. emboss revised this gist May 1, 2014. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions analysis.rb
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,5 @@
    # Original castealer.rb with analysis
    #####################################################################################

    require 'rubygems' #redundant
    require 'openssl'
    @@ -97,6 +98,7 @@
    puts root.verify(key)

    # Second, simplified PoC with analysis
    #####################################################################################
    require 'openssl'
    raw = File.read "root.cer"
    b = OpenSSL::PKey::RSA.new File.read 'root.key'
    @@ -132,6 +134,7 @@
    printf "Check spoof.cer"

    # keys.rb (proving that reloading the "spoofed" key file loads the original key)
    #####################################################################################

    require 'openssl'

    @@ -154,6 +157,7 @@
    puts "spoof_key == new_key: #{spoof_key.to_der == new_key.to_der}" #true

    # Simplified castealer.rb - IMHO, this covers all the important aspects of both PoC scripts
    #####################################################################################

    require 'openssl'

  6. emboss renamed this gist May 1, 2014. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  7. emboss created this gist May 1, 2014.
    163 changes: 163 additions & 0 deletions gistfile1.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,163 @@
    # Original castealer.rb with analysis

    require 'rubygems' #redundant
    require 'openssl'
    require 'digest/md5' #redundant

    key = OpenSSL::PKey::RSA.new(2048)
    cipher = OpenSSL::Cipher::AES.new(256, :CBC) #redundant
    ctx = OpenSSL::SSL::SSLContext.new #redundant
    puts "Spoof must be in DER format and saved as root.cer"
    raw = File.read "root.cer"

    cert = OpenSSL::X509::Certificate.new raw

    # The following lines are redundant. What this code does is it
    # creates a copy of the original certificate.
    # ef.issuer_certificate' is what's going to be signed below,
    # it would be equivalent to simply sign 'certificate' instead.
    # IMHO it doesn't matter if the certificate is changed or not
    # in any way for what is to follow.
    cert.version = 2
    ef = OpenSSL::X509::ExtensionFactory.new
    ef.issuer_certificate = OpenSSL::X509::Certificate.new raw
    cert.subject = ef.issuer_certificate.subject
    ef.subject_certificate = ef.issuer_certificate
    cert.issuer = ef.issuer_certificate.issuer
    cert.serial = ef.issuer_certificate.serial
    ctx.key = ef.issuer_certificate.public_key
    cert.public_key = ef.issuer_certificate.public_key
    cert.not_after = ef.issuer_certificate.not_after
    cert.not_before = ef.issuer_certificate.not_before
    cert.extensions = ef.issuer_certificate.extensions

    # Note that two distinct keys are written to the file 'root.key'.
    # The first one is the original certificate's public key, the
    # second one is the public/private key pair of the newly created
    # RSA key. The resulting file looks like this:
    #
    #-----BEGIN PUBLIC KEY-----
    #<original certificate's public key>
    #-----END PUBLIC KEY-----
    #-----BEGIN RSA PRIVATE KEY-----
    #<public private key/pair of new RSA key>
    #-----END RSA PRIVATE KEY-----
    #
    # 'spoof' and 'key' both represent the same key
    a = File.open("root"".key", "w")
    a.syswrite("#{cert.public_key}") # 'syswrite' or normal 'write', no difference
    a.syswrite("#{key.to_pem}") # 'syswrite' or normal 'write', no difference

    # This simply reloads the new RSA key, cf. 'keys.rb' for proof.
    # The certificate's original public key is simply ignored.
    spoof = OpenSSL::PKey::RSA.new File.read 'root.key'

    printf "Verifying Keys Work: "
    puts spoof.private? # true (it's the new key, after all)
    ctx.cert = ef.issuer_certificate #redundant
    puts "============================================================="

    # This effectively resigns the original certificate with the new key
    root = ef.issuer_certificate.sign(spoof, OpenSSL::Digest::SHA1.new)

    # Not important, just saving objects involved in files
    filer = File.open("#{cert.serial}"".key", "w")
    filer.syswrite("#{spoof.to_pem}")
    file = File.open("spoof"".cer", "w")
    file.syswrite("#{cert.to_der}")
    files = File.open("#{cert.serial}"".pem", "w")
    files.syswrite("#{cert.to_pem}")
    files.syswrite("#{spoof.to_pem}")
    puts "Hijacked Certificate with chainloaded key saved @ #{cert.serial}.pem"
    printf "Verifying Keys Intergity: "

    # This prints 'true', but this is expected behavior. We resigned the certificate
    # with the new key, and all that X509Certificate#verify does is to check whether
    # a certificate's signature is valid given a certain private key. It *does not*
    # check whether the private key actually matches the public key which is part of
    # the certificate.
    # While it certainly is surprising at first that X509Certificate#verify reports
    # "true" in this case, this is still correct behavior. Since we resigned the
    # certificate, it faithfully reports that the signature is correct. While the
    # signature value matches, it is true that the public and private key do not. But
    # this is not a security-critical issue IMO. A certificate resigned this way
    # cannot be used for anything meaningful unless you tricked somebody into explicitly
    # trusting it.
    # In fact, OpenSSL itself doesn't check whether keys match for root certificates.
    # We trust root certificates merely by possession (i.e. they are part of our
    # preconfigured 'trust stores').
    # Everybody could generate a valid self-signed root
    # certifcate, whose data may even match exactly that of a real CA root certificate.
    # But they are not included in any trust store (browser, OS, etc.) and thus useless
    # unless you explicitly/actively want to trust such a certificate.
    # If we spoofed an intermediate or end entity certificate in this way, it wouldn't
    # help either because the signature would no longer be accepted during the PKIX
    # validation process (where the signature of the spoofed certificate would be checked
    # against the public key of the supposed issuing certificate and consequently fail).
    puts root.verify(key)

    # Second, simplified PoC with analysis
    require 'openssl'
    raw = File.read "root.cer"
    b = OpenSSL::PKey::RSA.new File.read 'root.key'
    cert = OpenSSL::X509::Certificate.new raw

    # The following lines are redundant regarding the final
    # outcome
    issuer_certificate = OpenSSL::X509::Certificate.new raw
    cert.subject = issuer_certificate.subject
    subject_certificate = issuer_certificate
    cert.issuer = issuer_certificate.issuer
    cert.serial = issuer_certificate.serial
    cert.public_key = issuer_certificate.public_key
    cert.not_after = issuer_certificate.not_after
    cert.not_before = issuer_certificate.not_before
    cert.extensions = issuer_certificate.extensions

    # Again, this just reloads 'b', as in the original PoC.
    # Cf. 'keys.rb' for proof.
    a = File.open("spoof"".key", "w")
    a.syswrite("#{cert.public_key}")
    a.syswrite("#{b.to_pem}")

    # 'spoof' is the same as 'b'
    spoof = OpenSSL::PKey::RSA.new File.read 'spoof.key'

    # The certificate is resigned, so the same considerations
    # apply as for the original PoC.
    root = cert.sign(spoof, OpenSSL::Digest::SHA1.new)
    file = File.open("spoof"".cer", "w")
    file.syswrite("#{root.to_der}")
    file.syswrite("#{cert.to_der}")
    printf "Check spoof.cer"

    # keys.rb (proving that reloading the "spoofed" key file loads the original key)

    require 'openssl'

    raw = File.read 'root.pem'
    new_key = OpenSSL::PKey::RSA.new(2048)

    cert = OpenSSL::X509::Certificate.new raw
    public_key = cert.public_key

    File.open('spoof.key', 'w') do |f|
    f.write(cert.public_key)
    f.write(new_key.to_pem)
    end

    spoof_key = OpenSSL::PKey::RSA.new(File.read('spoof.key'))

    puts "cert.public_key == public_key: #{cert.public_key.to_der == public_key.to_der}" # true
    puts "spoof_key.public_key == public_key: #{spoof_key.public_key.to_der == public_key.to_der}" # false
    puts "spoof_key.public_key == new_key.public_key: #{spoof_key.public_key.to_der == new_key.public_key.to_der}" #true
    puts "spoof_key == new_key: #{spoof_key.to_der == new_key.to_der}" #true

    # Simplified castealer.rb - IMHO, this covers all the important aspects of both PoC scripts

    require 'openssl'

    spoof = OpenSSL::PKey::RSA.new(2048)
    cert = OpenSSL::X509::Certificate.new(File.read("some_certificate.cer"))
    root = cert.sign(spoof, OpenSSL::Digest::SHA1.new) # original certificate, resigned with new RSA key
    puts "New Root certificate verifies with the new key? #{root.verify(key)}" # true and expected