Skip to content

Commit 96d458b

Browse files
committed
Add a function to validate an x509 RSA key pair
Add a function to validate an x509 RSA certificate and key pair, as commonly used for TLS certificates. The rationale behind this is that we store our TLS certificates and private keys in Hiera YAML files, and poor indentation or formatting in the YAML file could cause a valid certificate to be considered invalid. Will cause the Puppet run to fail if: - an invalid certificate is detected - an invalid RSA key is detected - the certificate does not match the key, i.e. the certificate has not been signed by the supplied key The test certificates I've used in the spec tests were generated using the Go standard library: $ go run $GOROOT/src/crypto/tls/generate_cert.go -host localhost Example output: ==> cache-1.router: Error: Not a valid RSA key: Neither PUB key nor PRIV key:: nested asn1 error at /var/govuk/puppet/modules/nginx/manifests/config/ssl.pp:30 on node cache-1.router.dev.gov.uk
1 parent ef0c13b commit 96d458b

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-0
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
module Puppet::Parser::Functions
2+
3+
newfunction(:validate_x509_rsa_key_pair, :doc => <<-ENDHEREDOC
4+
Validates a PEM-formatted X509 certificate and private
5+
key using OpenSSL. Verifies that the certficate's
6+
signature was created from the supplied key.
7+
Fail compilation if any value fails this check.
8+
9+
validate_x509_rsa_key_pair($cert, $key)
10+
11+
ENDHEREDOC
12+
) do |args|
13+
14+
require 'openssl'
15+
16+
NUM_ARGS ||= 2
17+
18+
unless args.length == NUM_ARGS then
19+
raise Puppet::ParseError,
20+
("validate_x509_rsa_key_pair(): wrong number of arguments (#{args.length}; must be #{NUM_ARGS})")
21+
end
22+
23+
args.each do |arg|
24+
unless arg.is_a?(String)
25+
raise Puppet::ParseError, "#{arg.inspect} is not a string."
26+
end
27+
end
28+
29+
begin
30+
cert = OpenSSL::X509::Certificate.new(args[0])
31+
rescue OpenSSL::X509::CertificateError => e
32+
raise Puppet::ParseError, "Not a valid x509 certificate: #{e}"
33+
end
34+
35+
begin
36+
key = OpenSSL::PKey::RSA.new(args[1])
37+
rescue OpenSSL::PKey::RSAError => e
38+
raise Puppet::ParseError, "Not a valid RSA key: #{e}"
39+
end
40+
41+
unless cert.verify(key)
42+
raise Puppet::ParseError, "Certificate signature does not match supplied key"
43+
end
44+
end
45+
46+
end
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
require 'spec_helper'
2+
3+
describe 'validate_x509_rsa_key_pair' do
4+
5+
let(:valid_cert) do
6+
<<EOS
7+
-----BEGIN CERTIFICATE-----
8+
MIIC9jCCAeCgAwIBAgIRAK11n3X7aypJ7FPM8UFyAeowCwYJKoZIhvcNAQELMBIx
9+
EDAOBgNVBAoTB0FjbWUgQ28wHhcNMTUxMTIzMjIzOTU4WhcNMTYxMTIyMjIzOTU4
10+
WjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
11+
CgKCAQEAz9bY/piKahD10AiJSfbI2A8NG5UwRz0r9T/WfvNVdhgrsGFgNQjvpUoZ
12+
nNJpQIHBbgMOiXqfATFjJl5FjEkSf7GUHohlGVls9MX2JmVvknzsiitd75H/EJd+
13+
N+k915lix8Vqmj8d1CTlbF/8tEjzANI67Vqw5QTuqebO7rkIUvRg6yiRfSo75FK1
14+
RinCJyl++kmleBwQZBInQyg95GvJ5JTqMzBs67DeeyzskDhTeTePRYVF2NwL8QzY
15+
htvLIBERTNsyU5i7nkxY5ptUwgFUwd93LH4Q19tPqL5C5RZqXxhE51thOOwafm+a
16+
W/cRkqYqV+tv+j1jJ3WICyF1JNW0BQIDAQABo0swSTAOBgNVHQ8BAf8EBAMCAKAw
17+
EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALggls
18+
b2NhbGhvc3QwCwYJKoZIhvcNAQELA4IBAQAzRo0hpVTrFQZLIXpwvKwZVGvJdCkV
19+
P95DTsSk/VTGV+/YtxrRqks++hJZnctm2PbnTsCAoIP3AMx+vicCKiKrxvpsLU8/
20+
+6cowUbcuGMdSQktwDqbAgEhQlLsETll06w1D/KC+ejOc4+LRn3GQcEyGDtMk/EX
21+
IeAvBZHr4/kVXWnfo6kzCLcku1f8yE/yDEFClZe9XV1Lk/s+3YfXVtNnMJJ1giZI
22+
QVOe6CkmuQq+4AtIeW8aLkvlfp632jag1F77a1y+L268koKkj0hBMrtcErVQaxmq
23+
xym0+soR4Tk4pTIGckeFglrLxkP2JpM/yTwSEAVlmG9vgTliYKyR0uMl
24+
-----END CERTIFICATE-----
25+
EOS
26+
end
27+
28+
let(:valid_key) do
29+
<<EOS
30+
-----BEGIN RSA PRIVATE KEY-----
31+
MIIEogIBAAKCAQEAz9bY/piKahD10AiJSfbI2A8NG5UwRz0r9T/WfvNVdhgrsGFg
32+
NQjvpUoZnNJpQIHBbgMOiXqfATFjJl5FjEkSf7GUHohlGVls9MX2JmVvknzsiitd
33+
75H/EJd+N+k915lix8Vqmj8d1CTlbF/8tEjzANI67Vqw5QTuqebO7rkIUvRg6yiR
34+
fSo75FK1RinCJyl++kmleBwQZBInQyg95GvJ5JTqMzBs67DeeyzskDhTeTePRYVF
35+
2NwL8QzYhtvLIBERTNsyU5i7nkxY5ptUwgFUwd93LH4Q19tPqL5C5RZqXxhE51th
36+
OOwafm+aW/cRkqYqV+tv+j1jJ3WICyF1JNW0BQIDAQABAoIBADAiZ/r+xP+vkd5u
37+
O61/lCBFzBlZQecdybJw6HJaVK6XBndA9hESUr4LHUdui6W+51ddKd65IV4bXAUk
38+
zCKjQb+FFvLDT/bA+TTvLATUdTSN7hJJ3OWBAHuNOlQklof6JCB0Hi4+89+P8/pX
39+
eKUgR/cmuTMDT/iaXdPHeqFbBQyA1ZpQFRjN5LyyJMS/9FkywuNc5wlpsArtc51T
40+
gIKENUZCuPhosR+kMFc2iuTNvqZWPhvouSrmhi2O6nSqV+oy0+irlqSpCF2GsCI8
41+
72TtLpq94Grrq0BEH5avouV+Lp4k83vO65OKCQKUFQlxz3Xkxm2U3J7KzxqnRtM3
42+
/b+cJ/kCgYEA6/yOnaEYhH/7ijhZbPn8RujXZ5VGJXKJqIuaPiHMmHVS5p1j6Bah
43+
2PcnqJA2IlLs3UloN+ziAxAIH6KCBiwlQ/uPBNMMaJsIjPNBEy8axjndKhKUpidg
44+
R0OJ7RQqMShOJ8akrSfWdPtXC/GBuwCYE//t77GgZaIMO3FcT9EKA48CgYEA4Xcx
45+
Fia0Jg9iyAhNmUOXI6hWcGENavMx01+x7XFhbnMjIKTZevFfTnTkrX6HyLXyGtMU
46+
gHOn+k4PE/purI4ARrKO8m5wYEKqSIt4dBMTkIXXirfQjXgfjR8E4T/aPe5fOFZo
47+
7OYuxLRtzmG1C2sW4txwKAKX1LaWcVx/RLSttSsCgYBbcj8Brk+F6OJcqYFdzXGJ
48+
OOlf5mSMVlopyg83THmwCqbZXtw8L6kAHqZrl5airmfDSJLuOQlMDoZXW+3u3mSC
49+
d5TwVahVUN57YDgzaumBLyMZDqIz0MZqVy23hTzkV64Rk9R0lR9xrYQJyMhw4sYL
50+
2f0mCTsSpzz+O+t9so+i2QKBgEC38gMlwPhb2kMI/x1LZYr6uzUu5qcYf+jowy4h
51+
KZKGwkKQj0zXFEB1FV8nvtpCP+irRmtIx6L13SYi8LnfWPzyLE4ynVdES5TfVAgd
52+
obQOdzx+XwL8xDHCAaiWp5K3ZeXKB/xYZnxYPlzLdyh76Ond1OPnOqX4c16+6llS
53+
c7pZAoGATd9NckT0XtXLEsF3IraDivq8dP6bccX2DNfS8UeEvRRrRwpFpSRrmuGb
54+
jbG4yzoIX4RjQfj/z48hwhJB+cKiN9WwcPsFXtHe7v3F6BRwK0JUfrCiXad8/SGZ
55+
KAf7Dfqi608zBdnPWHacre2Y35gPHB00nFQOLS6u46aBNSq07YA=
56+
-----END RSA PRIVATE KEY-----
57+
EOS
58+
end
59+
60+
let(:another_valid_key) do
61+
<<EOS
62+
-----BEGIN RSA PRIVATE KEY-----
63+
MIIEpAIBAAKCAQEAoISxYJBTPAeAzFnm+lE/ljLlmGal2Xr3vwZKkvJiuKA/m4QJ
64+
0ZNdtkBSDOVuG2dXVv6W4sChRtsCdvuVe7bjTYvlU8TWM3VEJDL9l9cRXScxxlKQ
65+
Xwb35y1yV35NJfaK/jzm9KcErtQQs1RxvGlWRaohmLM8uQcuhjZfMsSlQoHQD5LX
66+
sbPtk82RPyxYc1dj2vsaoi1VvuP2+jv4xLQOmNJY1bT5GTurqiltmxEtWhNNmGg0
67+
2wtK00ifqLVO5HNc3gXQCDM2M99Sbmn1YtbrgsU9xMYfcPmvQvb+YoKskyoqck+c
68+
HR//hi7vslbxABrny15LBkEfRc4TickphSGYXwIDAQABAoIBAATEzGw8/WwMIQRx
69+
K06GeWgh7PZBHm4+m/ud2TtSXiJ0CE+7dXs3cJJIiOd/LW08/bhE6gCkjmYHfaRB
70+
Ryicv1X/cPmzIFX5BuQ4a5ZGOmrVDkKBE27vSxAgJoR46RvWnjx9XLMp/xaekDxz
71+
psldK8X4DvV1ZbltgDFWji947hvyqUtHdKnkQnc5j7aCIFJf9GMfzaeeDPMaL8WF
72+
mVL4iy9EAOjNOHBshZj/OHyU5FbJ8ROwZQlCOiLCdFegftSIXt8EYDnjB3BdsALH
73+
N6hquqrD7xDKyRbTD0K7lqxUubuMwTQpi61jZD8TBTXEPyFVAnoMpXkc0Y+np40A
74+
YiIsR+kCgYEAyrc4Bh6fb9gt49IXGXOSRZ5i5+TmJho4kzIONrJ7Ndclwx9wzHfh
75+
eGBodWaw5CxxQGMf4vEiaZrpAiSFeDffBLR+Wa2TFE5aWkdYkR34maDjO00m4PE1
76+
S+YsZoGw7rGmmj+KS4qv2T26FEHtUI+F31RC1FPohLsQ22Jbn1ORipsCgYEAyrYB
77+
J2Ncf2DlX1C0GfxyUHQOTNl0V5gpGvpbZ0WmWksumYz2kSGOAJkxuDKd9mKVlAcz
78+
czmN+OOetuHTNqds2JJKKJy6hJbgCdd9aho3dId5Xs4oh4YwuFQiG8R/bJZfTlXo
79+
99Qr02L7MmDWYLmrR3BA/93UPeorHPtjqSaYU40CgYEAtmGfWwokIglaSDVVqQVs
80+
3YwBqmcrla5TpkMLvLRZ2/fktqfL4Xod9iKu+Klajv9ZKTfFkXWno2HHL7FSD/Yc
81+
hWwqnV5oDIXuDnlQOse/SeERb+IbD5iUfePpoJQgbrCQlwiB0TNGwOojR2SFMczf
82+
Ai4aLlQLx5dSND9K9Y7HS+8CgYEAixlHQ2r4LuQjoTs0ytwi6TgqE+vn3K+qDTwc
83+
eoods7oBWRaUn1RCKAD3UClToZ1WfMRQNtIYrOAsqdveXpOWqioAP0wE5TTOuZIo
84+
GiWxRgIsc7TNtOmNBv+chCdbNP0emxdyjJUIGb7DFnfCw47EjHnn8Guc13uXaATN
85+
B2ZXgoUCgYAGa13P0ggUf5BMJpBd8S08jKRyvZb1CDXcUCuGtk2yEx45ern9U5WY
86+
zJ13E5z9MKKO8nkGBqrRfjJa8Xhxk4HKNFuzHEet5lvNE7IKCF4YQRb0ZBhnb/78
87+
+4ZKjFki1RrWRNSw9TdvrK6qaDKgTtCTtfRVXAYQXUgq7lSFOTtL3A==
88+
-----END RSA PRIVATE KEY-----
89+
EOS
90+
end
91+
92+
let(:valid_cert_but_indented) do
93+
valid_cert.gsub(/^/, ' ')
94+
end
95+
96+
let(:valid_key_but_indented) do
97+
valid_key.gsub(/^/, ' ')
98+
end
99+
100+
let(:bad_cert) do
101+
'foo'
102+
end
103+
104+
let(:bad_key) do
105+
'bar'
106+
end
107+
108+
context 'function signature validation' do
109+
it { is_expected.not_to eq(nil) }
110+
it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) }
111+
it { is_expected.to run.with_params(0, 1, 2, 3).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) }
112+
end
113+
114+
context 'valid input' do
115+
describe 'valid certificate and key' do
116+
it { is_expected.to run.with_params(valid_cert, valid_key) }
117+
end
118+
end
119+
120+
context 'bad input' do
121+
describe 'valid but indented certificate, valid key' do
122+
it { is_expected.to run.with_params(valid_cert_but_indented, valid_key).and_raise_error(Puppet::ParseError, /Not a valid x509 certificate/) }
123+
end
124+
125+
describe 'valid certificate, valid but indented key' do
126+
it { is_expected.to run.with_params(valid_cert, valid_key_but_indented).and_raise_error(Puppet::ParseError, /Not a valid RSA key/) }
127+
end
128+
129+
describe 'valid certificate, bad key' do
130+
it { is_expected.to run.with_params(valid_cert, bad_key).and_raise_error(Puppet::ParseError, /Not a valid RSA key/) }
131+
end
132+
133+
describe 'bad certificate, valid key' do
134+
it { is_expected.to run.with_params(bad_cert, valid_key).and_raise_error(Puppet::ParseError, /Not a valid x509 certificate/) }
135+
end
136+
137+
describe 'validate certificate and key; certficate not signed by key' do
138+
it { is_expected.to run.with_params(valid_cert, another_valid_key).and_raise_error(Puppet::ParseError, /Certificate signature does not match supplied key/) }
139+
end
140+
141+
describe 'valid cert and key but arguments in wrong order' do
142+
it { is_expected.to run.with_params(valid_key, valid_cert).and_raise_error(Puppet::ParseError, /Not a valid x509 certificate/) }
143+
end
144+
145+
describe 'non-string arguments' do
146+
it { is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError, /is not a string/) }
147+
it { is_expected.to run.with_params(1, 1).and_raise_error(Puppet::ParseError, /is not a string/) }
148+
it { is_expected.to run.with_params(true, true).and_raise_error(Puppet::ParseError, /is not a string/) }
149+
end
150+
end
151+
end

0 commit comments

Comments
 (0)