Skip to content

Commit f341962

Browse files
pcarlislenicklewis
authored andcommitted
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".
1 parent 34b9c0b commit f341962

File tree

2 files changed

+87
-11
lines changed

2 files changed

+87
-11
lines changed

lib/puppet/ssl/certificate_authority.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,17 @@ def check_internal_signing_policies(hostname, csr, allow_dns_alt_names)
303303
raise CertificateSigningError.new(hostname), "CSR has request extensions that are not permitted: #{names}"
304304
end
305305

306+
# Do not sign misleading CSRs
307+
cn = csr.content.subject.to_a.assoc("CN")[1]
308+
if hostname != cn
309+
raise CertificateSigningError.new(hostname), "CSR subject common name #{cn.inspect} does not match expected certname #{hostname.inspect}"
310+
end
311+
312+
# Only allow printing ascii characters, excluding /
313+
if hostname !~ /\A[ -.0-~]+\Z/
314+
raise CertificateSigningError.new(hostname), "CSR #{hostname.inspect} subject contains unprintable or non-ASCII characters"
315+
end
316+
306317
# Wildcards: we don't allow 'em at any point.
307318
#
308319
# The stringification here makes the content visible, and saves us having

spec/unit/ssl/certificate_authority_spec.rb

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def stub_ca_host
246246
# Stub out the factory
247247
Puppet::SSL::CertificateFactory.stubs(:build).returns "my real cert"
248248

249-
@request_content = stub "request content stub", :subject => @name
249+
@request_content = stub "request content stub", :subject => OpenSSL::X509::Name.new([['CN', @name]])
250250
@request = stub 'request', :name => @name, :request_extensions => [], :subject_alt_names => [], :content => @request_content
251251

252252
# And the inventory
@@ -303,28 +303,28 @@ def stub_ca_host
303303

304304
it "should use a certificate type of :ca" do
305305
Puppet::SSL::CertificateFactory.expects(:build).with do |*args|
306-
args[0] == :ca
306+
args[0].should == :ca
307307
end.returns "my real cert"
308308
@ca.sign(@name, :ca, @request)
309309
end
310310

311311
it "should pass the provided CSR as the CSR" do
312312
Puppet::SSL::CertificateFactory.expects(:build).with do |*args|
313-
args[1] == @request
313+
args[1].should == @request
314314
end.returns "my real cert"
315315
@ca.sign(@name, :ca, @request)
316316
end
317317

318318
it "should use the provided CSR's content as the issuer" do
319319
Puppet::SSL::CertificateFactory.expects(:build).with do |*args|
320-
args[2].subject == "myhost"
320+
args[2].subject.to_s.should == "/CN=myhost"
321321
end.returns "my real cert"
322322
@ca.sign(@name, :ca, @request)
323323
end
324324

325325
it "should pass the next serial as the serial number" do
326326
Puppet::SSL::CertificateFactory.expects(:build).with do |*args|
327-
args[3] == @serial
327+
args[3].should == @serial
328328
end.returns "my real cert"
329329
@ca.sign(@name, :ca, @request)
330330
end
@@ -452,11 +452,76 @@ def stub_ca_host
452452
@cert.stubs :save
453453
end
454454

455+
it "should reject CSRs whose CN doesn't match the name for which we're signing them" do
456+
# Shorten this so the test doesn't take too long
457+
Puppet[:keylength] = 1024
458+
key = Puppet::SSL::Key.new('the_certname')
459+
key.generate
460+
461+
csr = Puppet::SSL::CertificateRequest.new('the_certname')
462+
csr.generate(key)
463+
464+
expect do
465+
@ca.check_internal_signing_policies('not_the_certname', csr, false)
466+
end.to raise_error(
467+
Puppet::SSL::CertificateAuthority::CertificateSigningError,
468+
/common name "the_certname" does not match expected certname "not_the_certname"/
469+
)
470+
end
471+
472+
describe "when validating the CN" do
473+
before :all do
474+
Puppet[:keylength] = 1024
475+
@signing_key = Puppet::SSL::Key.new('my_signing_key')
476+
@signing_key.generate
477+
end
478+
479+
[
480+
'completely_okay',
481+
'sure, why not? :)',
482+
'so+many(things)-are=allowed.',
483+
'this"is#just&madness%you[see]',
484+
'and even a (an?) \\!',
485+
'waltz, nymph, for quick jigs vex bud.',
486+
'{552c04ca-bb1b-11e1-874b-60334b04494e}'
487+
].each do |name|
488+
it "should accept #{name.inspect}" do
489+
csr = Puppet::SSL::CertificateRequest.new(name)
490+
csr.generate(@signing_key)
491+
492+
@ca.check_internal_signing_policies(name, csr, false)
493+
end
494+
end
495+
496+
[
497+
'super/bad',
498+
"not\neven\tkind\rof",
499+
"ding\adong\a",
500+
"hidden\b\b\b\b\b\bmessage",
501+
"☃ :("
502+
].each do |name|
503+
it "should reject #{name.inspect}" do
504+
# We aren't even allowed to make objects with these names, so let's
505+
# stub that to simulate an invalid one coming from outside Puppet
506+
Puppet::SSL::CertificateRequest.stubs(:validate_certname)
507+
csr = Puppet::SSL::CertificateRequest.new(name)
508+
csr.generate(@signing_key)
509+
510+
expect do
511+
@ca.check_internal_signing_policies(name, csr, false)
512+
end.to raise_error(
513+
Puppet::SSL::CertificateAuthority::CertificateSigningError,
514+
/subject contains unprintable or non-ASCII characters/
515+
)
516+
end
517+
end
518+
end
519+
455520
it "should reject a critical extension that isn't on the whitelist" do
456521
@request.stubs(:request_extensions).returns [{ "oid" => "banana",
457522
"value" => "yumm",
458523
"critical" => true }]
459-
expect { @ca.sign(@name) }.to raise_error(
524+
expect { @ca.check_internal_signing_policies(@name, @request, false) }.to raise_error(
460525
Puppet::SSL::CertificateAuthority::CertificateSigningError,
461526
/request extensions that are not permitted/
462527
)
@@ -466,7 +531,7 @@ def stub_ca_host
466531
@request.stubs(:request_extensions).returns [{ "oid" => "peach",
467532
"value" => "meh",
468533
"critical" => false }]
469-
expect { @ca.sign(@name) }.to raise_error(
534+
expect { @ca.check_internal_signing_policies(@name, @request, false) }.to raise_error(
470535
Puppet::SSL::CertificateAuthority::CertificateSigningError,
471536
/request extensions that are not permitted/
472537
)
@@ -479,15 +544,15 @@ def stub_ca_host
479544
{ "oid" => "subjectAltName",
480545
"value" => "DNS:foo",
481546
"critical" => true }]
482-
expect { @ca.sign(@name) }.to raise_error(
547+
expect { @ca.check_internal_signing_policies(@name, @request, false) }.to raise_error(
483548
Puppet::SSL::CertificateAuthority::CertificateSigningError,
484549
/request extensions that are not permitted/
485550
)
486551
end
487552

488553
it "should reject a subjectAltName for a non-DNS value" do
489554
@request.stubs(:subject_alt_names).returns ['DNS:foo', 'email:bar@example.com']
490-
expect { @ca.sign(@name, true) }.to raise_error(
555+
expect { @ca.check_internal_signing_policies(@name, @request, true) }.to raise_error(
491556
Puppet::SSL::CertificateAuthority::CertificateSigningError,
492557
/subjectAltName outside the DNS label space/
493558
)
@@ -497,15 +562,15 @@ def stub_ca_host
497562
@request.content.stubs(:subject).
498563
returns(OpenSSL::X509::Name.new([["CN", "*.local"]]))
499564

500-
expect { @ca.sign(@name) }.to raise_error(
565+
expect { @ca.check_internal_signing_policies('*.local', @request, false) }.to raise_error(
501566
Puppet::SSL::CertificateAuthority::CertificateSigningError,
502567
/subject contains a wildcard/
503568
)
504569
end
505570

506571
it "should reject a wildcard subjectAltName" do
507572
@request.stubs(:subject_alt_names).returns ['DNS:foo', 'DNS:*.bar']
508-
expect { @ca.sign(@name, true) }.to raise_error(
573+
expect { @ca.check_internal_signing_policies(@name, @request, true) }.to raise_error(
509574
Puppet::SSL::CertificateAuthority::CertificateSigningError,
510575
/subjectAltName contains a wildcard/
511576
)

0 commit comments

Comments
 (0)