-
Notifications
You must be signed in to change notification settings - Fork 579
Add a function to validate an x509 RSA key pair #552
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
module Puppet::Parser::Functions | ||
|
||
newfunction(:validate_x509_rsa_key_pair, :doc => <<-ENDHEREDOC | ||
Validates a PEM-formatted X.509 certificate and RSA private key using | ||
OpenSSL. Verifies that the certficate's signature was created from the | ||
supplied key. | ||
|
||
Fail compilation if any value fails this check. | ||
|
||
validate_x509_rsa_key_pair($cert, $key) | ||
|
||
ENDHEREDOC | ||
) do |args| | ||
|
||
require 'openssl' | ||
|
||
NUM_ARGS = 2 unless defined? NUM_ARGS | ||
|
||
unless args.length == NUM_ARGS then | ||
raise Puppet::ParseError, | ||
("validate_x509_rsa_key_pair(): wrong number of arguments (#{args.length}; must be #{NUM_ARGS})") | ||
end | ||
|
||
args.each do |arg| | ||
unless arg.is_a?(String) | ||
raise Puppet::ParseError, "#{arg.inspect} is not a string." | ||
end | ||
end | ||
|
||
begin | ||
cert = OpenSSL::X509::Certificate.new(args[0]) | ||
rescue OpenSSL::X509::CertificateError => e | ||
raise Puppet::ParseError, "Not a valid x509 certificate: #{e}" | ||
end | ||
|
||
begin | ||
key = OpenSSL::PKey::RSA.new(args[1]) | ||
rescue OpenSSL::PKey::RSAError => e | ||
raise Puppet::ParseError, "Not a valid RSA key: #{e}" | ||
end | ||
|
||
unless cert.verify(key) | ||
raise Puppet::ParseError, "Certificate signature does not match supplied key" | ||
end | ||
end | ||
|
||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
require 'spec_helper' | ||
|
||
describe 'validate_x509_rsa_key_pair' do | ||
|
||
let(:valid_cert) do | ||
<<EOS | ||
-----BEGIN CERTIFICATE----- | ||
MIIC9jCCAeCgAwIBAgIRAK11n3X7aypJ7FPM8UFyAeowCwYJKoZIhvcNAQELMBIx | ||
EDAOBgNVBAoTB0FjbWUgQ28wHhcNMTUxMTIzMjIzOTU4WhcNMTYxMTIyMjIzOTU4 | ||
WjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB | ||
CgKCAQEAz9bY/piKahD10AiJSfbI2A8NG5UwRz0r9T/WfvNVdhgrsGFgNQjvpUoZ | ||
nNJpQIHBbgMOiXqfATFjJl5FjEkSf7GUHohlGVls9MX2JmVvknzsiitd75H/EJd+ | ||
N+k915lix8Vqmj8d1CTlbF/8tEjzANI67Vqw5QTuqebO7rkIUvRg6yiRfSo75FK1 | ||
RinCJyl++kmleBwQZBInQyg95GvJ5JTqMzBs67DeeyzskDhTeTePRYVF2NwL8QzY | ||
htvLIBERTNsyU5i7nkxY5ptUwgFUwd93LH4Q19tPqL5C5RZqXxhE51thOOwafm+a | ||
W/cRkqYqV+tv+j1jJ3WICyF1JNW0BQIDAQABo0swSTAOBgNVHQ8BAf8EBAMCAKAw | ||
EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALggls | ||
b2NhbGhvc3QwCwYJKoZIhvcNAQELA4IBAQAzRo0hpVTrFQZLIXpwvKwZVGvJdCkV | ||
P95DTsSk/VTGV+/YtxrRqks++hJZnctm2PbnTsCAoIP3AMx+vicCKiKrxvpsLU8/ | ||
+6cowUbcuGMdSQktwDqbAgEhQlLsETll06w1D/KC+ejOc4+LRn3GQcEyGDtMk/EX | ||
IeAvBZHr4/kVXWnfo6kzCLcku1f8yE/yDEFClZe9XV1Lk/s+3YfXVtNnMJJ1giZI | ||
QVOe6CkmuQq+4AtIeW8aLkvlfp632jag1F77a1y+L268koKkj0hBMrtcErVQaxmq | ||
xym0+soR4Tk4pTIGckeFglrLxkP2JpM/yTwSEAVlmG9vgTliYKyR0uMl | ||
-----END CERTIFICATE----- | ||
EOS | ||
end | ||
|
||
let(:valid_key) do | ||
<<EOS | ||
-----BEGIN RSA PRIVATE KEY----- | ||
MIIEogIBAAKCAQEAz9bY/piKahD10AiJSfbI2A8NG5UwRz0r9T/WfvNVdhgrsGFg | ||
NQjvpUoZnNJpQIHBbgMOiXqfATFjJl5FjEkSf7GUHohlGVls9MX2JmVvknzsiitd | ||
75H/EJd+N+k915lix8Vqmj8d1CTlbF/8tEjzANI67Vqw5QTuqebO7rkIUvRg6yiR | ||
fSo75FK1RinCJyl++kmleBwQZBInQyg95GvJ5JTqMzBs67DeeyzskDhTeTePRYVF | ||
2NwL8QzYhtvLIBERTNsyU5i7nkxY5ptUwgFUwd93LH4Q19tPqL5C5RZqXxhE51th | ||
OOwafm+aW/cRkqYqV+tv+j1jJ3WICyF1JNW0BQIDAQABAoIBADAiZ/r+xP+vkd5u | ||
O61/lCBFzBlZQecdybJw6HJaVK6XBndA9hESUr4LHUdui6W+51ddKd65IV4bXAUk | ||
zCKjQb+FFvLDT/bA+TTvLATUdTSN7hJJ3OWBAHuNOlQklof6JCB0Hi4+89+P8/pX | ||
eKUgR/cmuTMDT/iaXdPHeqFbBQyA1ZpQFRjN5LyyJMS/9FkywuNc5wlpsArtc51T | ||
gIKENUZCuPhosR+kMFc2iuTNvqZWPhvouSrmhi2O6nSqV+oy0+irlqSpCF2GsCI8 | ||
72TtLpq94Grrq0BEH5avouV+Lp4k83vO65OKCQKUFQlxz3Xkxm2U3J7KzxqnRtM3 | ||
/b+cJ/kCgYEA6/yOnaEYhH/7ijhZbPn8RujXZ5VGJXKJqIuaPiHMmHVS5p1j6Bah | ||
2PcnqJA2IlLs3UloN+ziAxAIH6KCBiwlQ/uPBNMMaJsIjPNBEy8axjndKhKUpidg | ||
R0OJ7RQqMShOJ8akrSfWdPtXC/GBuwCYE//t77GgZaIMO3FcT9EKA48CgYEA4Xcx | ||
Fia0Jg9iyAhNmUOXI6hWcGENavMx01+x7XFhbnMjIKTZevFfTnTkrX6HyLXyGtMU | ||
gHOn+k4PE/purI4ARrKO8m5wYEKqSIt4dBMTkIXXirfQjXgfjR8E4T/aPe5fOFZo | ||
7OYuxLRtzmG1C2sW4txwKAKX1LaWcVx/RLSttSsCgYBbcj8Brk+F6OJcqYFdzXGJ | ||
OOlf5mSMVlopyg83THmwCqbZXtw8L6kAHqZrl5airmfDSJLuOQlMDoZXW+3u3mSC | ||
d5TwVahVUN57YDgzaumBLyMZDqIz0MZqVy23hTzkV64Rk9R0lR9xrYQJyMhw4sYL | ||
2f0mCTsSpzz+O+t9so+i2QKBgEC38gMlwPhb2kMI/x1LZYr6uzUu5qcYf+jowy4h | ||
KZKGwkKQj0zXFEB1FV8nvtpCP+irRmtIx6L13SYi8LnfWPzyLE4ynVdES5TfVAgd | ||
obQOdzx+XwL8xDHCAaiWp5K3ZeXKB/xYZnxYPlzLdyh76Ond1OPnOqX4c16+6llS | ||
c7pZAoGATd9NckT0XtXLEsF3IraDivq8dP6bccX2DNfS8UeEvRRrRwpFpSRrmuGb | ||
jbG4yzoIX4RjQfj/z48hwhJB+cKiN9WwcPsFXtHe7v3F6BRwK0JUfrCiXad8/SGZ | ||
KAf7Dfqi608zBdnPWHacre2Y35gPHB00nFQOLS6u46aBNSq07YA= | ||
-----END RSA PRIVATE KEY----- | ||
EOS | ||
end | ||
|
||
let(:another_valid_key) do | ||
<<EOS | ||
-----BEGIN RSA PRIVATE KEY----- | ||
MIIEpAIBAAKCAQEAoISxYJBTPAeAzFnm+lE/ljLlmGal2Xr3vwZKkvJiuKA/m4QJ | ||
0ZNdtkBSDOVuG2dXVv6W4sChRtsCdvuVe7bjTYvlU8TWM3VEJDL9l9cRXScxxlKQ | ||
Xwb35y1yV35NJfaK/jzm9KcErtQQs1RxvGlWRaohmLM8uQcuhjZfMsSlQoHQD5LX | ||
sbPtk82RPyxYc1dj2vsaoi1VvuP2+jv4xLQOmNJY1bT5GTurqiltmxEtWhNNmGg0 | ||
2wtK00ifqLVO5HNc3gXQCDM2M99Sbmn1YtbrgsU9xMYfcPmvQvb+YoKskyoqck+c | ||
HR//hi7vslbxABrny15LBkEfRc4TickphSGYXwIDAQABAoIBAATEzGw8/WwMIQRx | ||
K06GeWgh7PZBHm4+m/ud2TtSXiJ0CE+7dXs3cJJIiOd/LW08/bhE6gCkjmYHfaRB | ||
Ryicv1X/cPmzIFX5BuQ4a5ZGOmrVDkKBE27vSxAgJoR46RvWnjx9XLMp/xaekDxz | ||
psldK8X4DvV1ZbltgDFWji947hvyqUtHdKnkQnc5j7aCIFJf9GMfzaeeDPMaL8WF | ||
mVL4iy9EAOjNOHBshZj/OHyU5FbJ8ROwZQlCOiLCdFegftSIXt8EYDnjB3BdsALH | ||
N6hquqrD7xDKyRbTD0K7lqxUubuMwTQpi61jZD8TBTXEPyFVAnoMpXkc0Y+np40A | ||
YiIsR+kCgYEAyrc4Bh6fb9gt49IXGXOSRZ5i5+TmJho4kzIONrJ7Ndclwx9wzHfh | ||
eGBodWaw5CxxQGMf4vEiaZrpAiSFeDffBLR+Wa2TFE5aWkdYkR34maDjO00m4PE1 | ||
S+YsZoGw7rGmmj+KS4qv2T26FEHtUI+F31RC1FPohLsQ22Jbn1ORipsCgYEAyrYB | ||
J2Ncf2DlX1C0GfxyUHQOTNl0V5gpGvpbZ0WmWksumYz2kSGOAJkxuDKd9mKVlAcz | ||
czmN+OOetuHTNqds2JJKKJy6hJbgCdd9aho3dId5Xs4oh4YwuFQiG8R/bJZfTlXo | ||
99Qr02L7MmDWYLmrR3BA/93UPeorHPtjqSaYU40CgYEAtmGfWwokIglaSDVVqQVs | ||
3YwBqmcrla5TpkMLvLRZ2/fktqfL4Xod9iKu+Klajv9ZKTfFkXWno2HHL7FSD/Yc | ||
hWwqnV5oDIXuDnlQOse/SeERb+IbD5iUfePpoJQgbrCQlwiB0TNGwOojR2SFMczf | ||
Ai4aLlQLx5dSND9K9Y7HS+8CgYEAixlHQ2r4LuQjoTs0ytwi6TgqE+vn3K+qDTwc | ||
eoods7oBWRaUn1RCKAD3UClToZ1WfMRQNtIYrOAsqdveXpOWqioAP0wE5TTOuZIo | ||
GiWxRgIsc7TNtOmNBv+chCdbNP0emxdyjJUIGb7DFnfCw47EjHnn8Guc13uXaATN | ||
B2ZXgoUCgYAGa13P0ggUf5BMJpBd8S08jKRyvZb1CDXcUCuGtk2yEx45ern9U5WY | ||
zJ13E5z9MKKO8nkGBqrRfjJa8Xhxk4HKNFuzHEet5lvNE7IKCF4YQRb0ZBhnb/78 | ||
+4ZKjFki1RrWRNSw9TdvrK6qaDKgTtCTtfRVXAYQXUgq7lSFOTtL3A== | ||
-----END RSA PRIVATE KEY----- | ||
EOS | ||
end | ||
|
||
let(:valid_cert_but_indented) do | ||
valid_cert.gsub(/^/, ' ') | ||
end | ||
|
||
let(:valid_key_but_indented) do | ||
valid_key.gsub(/^/, ' ') | ||
end | ||
|
||
let(:malformed_cert) do | ||
truncate_middle(valid_cert) | ||
end | ||
|
||
let(:malformed_key) do | ||
truncate_middle(valid_key) | ||
end | ||
|
||
let(:bad_cert) do | ||
'foo' | ||
end | ||
|
||
let(:bad_key) do | ||
'bar' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be a better example, when these are strings that match PEM formatting, but contain invalid data; like missing a couple of lines in the middle. That way the deep checking of the function is proven. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good, I'll add that as an additional fixture to test. |
||
end | ||
|
||
context 'function signature validation' do | ||
it { is_expected.not_to eq(nil) } | ||
it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } | ||
it { is_expected.to run.with_params(0, 1, 2, 3).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } | ||
end | ||
|
||
context 'valid input' do | ||
describe 'valid certificate and key' do | ||
it { is_expected.to run.with_params(valid_cert, valid_key) } | ||
end | ||
end | ||
|
||
context 'bad input' do | ||
describe 'valid certificate, valid but indented key' do | ||
it { is_expected.to run.with_params(valid_cert, valid_key_but_indented).and_raise_error(Puppet::ParseError, /Not a valid RSA key/) } | ||
end | ||
|
||
describe 'valid certificate, malformed key' do | ||
it { is_expected.to run.with_params(valid_cert, malformed_key).and_raise_error(Puppet::ParseError, /Not a valid RSA key/) } | ||
end | ||
|
||
describe 'valid certificate, bad key' do | ||
it { is_expected.to run.with_params(valid_cert, bad_key).and_raise_error(Puppet::ParseError, /Not a valid RSA key/) } | ||
end | ||
|
||
describe 'valid but indented certificate, valid key' do | ||
it { is_expected.to run.with_params(valid_cert_but_indented, valid_key).and_raise_error(Puppet::ParseError, /Not a valid x509 certificate/) } | ||
end | ||
|
||
describe 'malformed certificate, valid key' do | ||
it { is_expected.to run.with_params(malformed_cert, valid_key).and_raise_error(Puppet::ParseError, /Not a valid x509 certificate/) } | ||
end | ||
|
||
describe 'bad certificate, valid key' do | ||
it { is_expected.to run.with_params(bad_cert, valid_key).and_raise_error(Puppet::ParseError, /Not a valid x509 certificate/) } | ||
end | ||
|
||
describe 'validate certificate and key; certficate not signed by key' do | ||
it { is_expected.to run.with_params(valid_cert, another_valid_key).and_raise_error(Puppet::ParseError, /Certificate signature does not match supplied key/) } | ||
end | ||
|
||
describe 'valid cert and key but arguments in wrong order' do | ||
it { is_expected.to run.with_params(valid_key, valid_cert).and_raise_error(Puppet::ParseError, /Not a valid x509 certificate/) } | ||
end | ||
|
||
describe 'non-string arguments' do | ||
it { is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError, /is not a string/) } | ||
it { is_expected.to run.with_params(1, 1).and_raise_error(Puppet::ParseError, /is not a string/) } | ||
it { is_expected.to run.with_params(true, true).and_raise_error(Puppet::ParseError, /is not a string/) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs a few string/non-string cases mixed to thoroughly test the validation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added some more tests to 97320ab. |
||
it { is_expected.to run.with_params("foo", {}).and_raise_error(Puppet::ParseError, /is not a string/) } | ||
it { is_expected.to run.with_params(1, "bar").and_raise_error(Puppet::ParseError, /is not a string/) } | ||
it { is_expected.to run.with_params("baz", true).and_raise_error(Puppet::ParseError, /is not a string/) } | ||
end | ||
end | ||
|
||
def truncate_middle(string) | ||
chars_to_truncate = 48 | ||
middle = (string.length / 2).floor | ||
start_pos = middle - (chars_to_truncate / 2) | ||
end_pos = middle + (chars_to_truncate / 2) | ||
|
||
string[start_pos...end_pos] = '' | ||
return string | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any OpenSSL error we can pass through to help debugging this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No.
cert.verify(key)
returns a boolean; either the key matches the certificate or it does not. If the key or cert are malformed or invalid, the earlier checks will catch that and report the error to the user.