CVE-2012-3867 (Ruby, published 2012-08-06T16:55Z)
Description: value: lib/puppet/ssl/certificate_authority.rb in Puppet before 2.6.17 and 2.7.x before 2.7.18, and Puppet Enterprise before 2.5.2, does not properly restrict the characters in the Common Name field of a Certificate Signing Request (CSR), which makes it easier for user-assisted remote attackers to trick administrators into signing a crafted agent certificate via ANSI control sequences.
Severity: MEDIUM (CVSS v3 base score: )
Weakness: CWE-264 \u2014 Permissions, Privileges, and Access Controls
Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
Repository: https://github.com/puppetlabs/puppet
Files changed:
lib/puppet/ssl/certificate_authority.rb
spec/unit/ssl/certificate_authority_spec.rb
Commit message: Validate CSR CN and provided certname before signing

This adds a few new checks when signing CSRs, to validate the CN. First,
it must conform to a small set of characters, which are the printable
ASCII characters, except for '/' (because we store these in files). This
prevents attacks such as a CN "foo^H^H^Hbar", which appears as "bar" to
"puppet cert list".

The other check is that the certname for the SSL::Host that we think
we're signing must match the CN. This prevents a CSR with the CN "foo"
from being submitted as a CSR for "bar", which would cause it to appear
as "bar" to "puppet cert list", but to issue a certificate for "foo".


Vulnerable code:
      @request_content = stub "request content stub", :subject => @name
          args[0] == :ca
          args[1] == @request
          args[2].subject == "myhost"
          args[3] == @serial
        expect { @ca.sign(@name) }.to raise_error(
        expect { @ca.sign(@name) }.to raise_error(
        expect { @ca.sign(@name) }.to raise_error(
        expect { @ca.sign(@name, true) }.to raise_error(
        expect { @ca.sign(@name) }.to raise_error(
        expect { @ca.sign(@name, true) }.to raise_error(

Fixed code:
    # Do not sign misleading CSRs
    cn = csr.content.subject.to_a.assoc("CN")[1]
    if hostname != cn
      raise CertificateSigningError.new(hostname), "CSR subject common name #{cn.inspect} does not match expected certname #{hostname.inspect}"
    end

    # Only allow printing ascii characters, excluding /
    if hostname !~ /\A[ -.0-~]+\Z/
      raise CertificateSigningError.new(hostname), "CSR #{hostname.inspect} subject contains unprintable or non-ASCII characters"
    end

      @request_content = stub "request content stub", :subject => OpenSSL::X509::Name.new([['CN', @name]])
          args[0].should == :ca
          args[1].should == @request
          args[2].subject.to_s.should == "/CN=myhost"
          args[3].should == @serial
      it "should reject CSRs whose CN doesn't match the name for which we're signing them" do
        # Shorten this so the test doesn't take too long
        Puppet[:keylength] = 1024
        key = Puppet::SSL::Key.new('the_certname')
        key.generate

        csr = Puppet::SSL::CertificateRequest.new('the_certname')
        csr.generate(key)

        expect do
          @ca.check_internal_signing_policies('not_the_certname', csr, false)
        end.to raise_error(
          Puppet::SSL::CertificateAuthority::CertificateSigningError,
          /common name "the_certname" does not match expected certname "not_the_certname"/
        )
      end

      describe "when validating the CN" do
        before :all do
          Puppet[:keylength] = 1024
          @signing_key = Puppet::SSL::Key.new('my_signing_key')
          @signing_key.generate
        end

        [
         'completely_okay',
         'sure, why not? :)',
         'so+many(things)-are=allowed.',
         'this"is#just&madness%you[see]',
         'and even a (an?) \\!',
         'waltz, nymph, for quick jigs vex bud.',
         '{552c04ca-bb1b-11e1-874b-60334b04494e}'
        ].each do |name|
          it "should accept #{name.inspect}" do
            csr = Puppet::SSL::CertificateRequest.new(name)
            csr.generate(@signing_key)

            @ca.check_internal_signing_policies(name, csr, false)
          end
        end

        [
         'super/bad',
         "not\neven\tkind\rof",
         "ding\adong\a",
         "hidden\b\b\b\b\b\bmessage",
         "☃ :("
        ].each do |name|
          it "should reject #{name.inspect}" do
            # We aren't even allowed to make objects with these names, so let's
            # stub that to simulate an invalid one coming from outside Puppet
            Puppet::SSL::CertificateRequest.stubs(:validate_certname)
            csr = Puppet::SSL::CertificateRequest.new(name)
            csr.generate(@signing_key)

            expect do
              @ca.check_internal_signing_policies(name, csr, false)
            end.to raise_error(
              Puppet::SSL::CertificateAuthority::CertificateSigningError,
              /subject contains unprintable or non-ASCII characters/
            )
          end
        end
      end

        expect { @ca.check_internal_signing_policies(@name, @request, false) }.to raise_error(
        expect { @ca.check_internal_signing_policies(@name, @request, false) }.to raise_error(
        expect { @ca.check_internal_signing_policies(@name, @request, false) }.to raise_error(
        expect { @ca.check_internal_signing_policies(@name, @request, true) }.to raise_error(
        expect { @ca.check_internal_signing_policies('*.local', @request, false) }.to raise_error(
        expect { @ca.check_internal_signing_policies(@name, @request, true) }.to raise_error(