Commit fda1bd8

mokha <mokha@cisco.com>
2018-02-08 21:16:41
try to extract the certificate safely.
1 parent 586e39c
Changed files (3)
lib/xml/kit/certificate.rb
@@ -2,11 +2,15 @@ module Xml
   module Kit
     # {include:file:spec/xml/certificate_spec.rb}
     class Certificate
+      BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
       BEGIN_CERT=/-----BEGIN CERTIFICATE-----/
       END_CERT=/-----END CERTIFICATE-----/
       # The use can be `:signing` or `:encryption`. Use `nil` for both.
       attr_reader :use
 
+      # The raw certificate value. This can be a Base64 encoded PEM or just a PEM format.
+      attr_reader :value
+
       def initialize(value, use: nil)
         @value = value
         @use = use.nil? ? use : use.downcase.to_sym
@@ -44,7 +48,7 @@ module Xml
       #
       # return [OpenSSL::X509::Certificate] the OpenSSL equivalent.
       def x509
-        self.class.to_x509(value)
+        @x509 ||= self.class.to_x509(value)
       end
 
       # Returns the public key.
@@ -79,18 +83,31 @@ module Xml
       end
 
       def stripped
-        value.to_s.gsub(BEGIN_CERT, '').gsub(END_CERT, '').gsub(/\n/, '')
+        self.class.strip(x509.to_pem)
+      end
+
+      def to_key_pair(private_key, passphrase: nil, use: nil)
+        KeyPair.new(x509.to_pem, private_key.to_s, passphrase, use)
       end
 
       def self.to_x509(value)
-        OpenSSL::X509::Certificate.new(Base64.decode64(value))
-      rescue OpenSSL::X509::CertificateError
+        value = Base64.decode64(strip(value)) if base64?(value)
         OpenSSL::X509::Certificate.new(value)
       end
 
-      private
+      def self.base64?(value)
+        return unless value.is_a?(String)
 
-      attr_reader :value
+        sanitized_value = strip(value)
+        !!sanitized_value.match(BASE64_FORMAT)
+      end
+
+      def self.strip(value)
+        value.
+          gsub(BEGIN_CERT, '').
+          gsub(END_CERT, '').
+          gsub(/[\r\n]|\\r|\\n|\s/, "")
+      end
     end
   end
 end
lib/xml/kit/document.rb
@@ -59,16 +59,23 @@ module Xml
 
         x509_certificates.each do |certificate|
           inactive = now < certificate.not_before
-          errors.add(:certificate, "Not valid before #{certificate.not_before}") if inactive
+          if inactive
+            error_message = "Not valid before #{certificate.not_before}"
+            errors.add(:certificate, error_message)
+          end
 
           expired = now > certificate.not_after
-          errors.add(:certificate, "Not valid after #{certificate.not_after}") if expired
+          if expired
+            error_message = "Not valid after #{certificate.not_after}"
+            errors.add(:certificate, error_message)
+          end
         end
       end
 
       def x509_certificates
-        xpath = "//ds:KeyInfo/ds:X509Data/ds:X509Certificate"
-        find_all(xpath).map { |item| Certificate.to_x509(item.text) }
+        find_all("//ds:KeyInfo/ds:X509Data/ds:X509Certificate").map do |item|
+          Certificate.to_x509(item.text)
+        end
       end
     end
   end
spec/xml/document_spec.rb
@@ -59,14 +59,15 @@ RSpec.describe Xml::Kit::Document do
       end
       let(:private_key) { OpenSSL::PKey::RSA.new(2048) }
       let(:digest_algorithm) { OpenSSL::Digest::SHA256.new }
+      let(:item) { Item.new }
 
       before :each do
         expired_certificate.sign(private_key, digest_algorithm)
       end
 
       it 'is invalid' do
-        item = Item.new
-        item.sign_with(::Xml::Kit::KeyPair.new(expired_certificate.to_pem, private_key.to_s, nil, :signing))
+        certificate = ::Xml::Kit::Certificate.new(expired_certificate)
+        item.sign_with(certificate.to_key_pair(private_key))
         subject = described_class.new(item.to_xml)
         expect(subject).to be_invalid
         expect(subject.errors[:certificate]).to be_present