Skip to content

Commit cd3baf0

Browse files
committed
Tests: preferred certificate chain support.
1 parent 1b01dee commit cd3baf0

File tree

2 files changed

+217
-4
lines changed

2 files changed

+217
-4
lines changed

t/acme_preferred_chain.t

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#!/usr/bin/perl
2+
3+
# Copyright (c) F5, Inc.
4+
#
5+
# This source code is licensed under the Apache License, Version 2.0 license
6+
# found in the LICENSE file in the root directory of this source tree.
7+
8+
# Tests for ACME client: preferred chain support.
9+
10+
###############################################################################
11+
12+
use warnings;
13+
use strict;
14+
15+
use Test::More;
16+
17+
use Net::SSLeay qw/ die_now /;
18+
19+
BEGIN { use FindBin; chdir($FindBin::Bin); }
20+
21+
use lib 'lib';
22+
use Test::Nginx;
23+
use Test::Nginx::ACME;
24+
use Test::Nginx::DNS;
25+
26+
###############################################################################
27+
28+
select STDERR; $| = 1;
29+
select STDOUT; $| = 1;
30+
31+
my $t = Test::Nginx->new()->has(qw/http http_ssl socket_ssl/)
32+
->has_daemon('openssl');
33+
34+
my $conf = <<'EOF';
35+
36+
%%TEST_GLOBALS%%
37+
38+
daemon off;
39+
40+
events {
41+
}
42+
43+
http {
44+
%%TEST_GLOBALS_HTTP%%
45+
46+
resolver 127.0.0.1:%%PORT_8980_UDP%%;
47+
48+
acme_issuer default {
49+
uri https://acme.test:%%PORT_9000%%/dir;
50+
ssl_trusted_certificate acme.test.crt;
51+
state_path %%TESTDIR%%/acme_default;
52+
accept_terms_of_service;
53+
}
54+
55+
acme_issuer chain1 {
56+
uri https://acme.test:%%PORT_9000%%/dir;
57+
preferred_chain "%%ISSUER_NAME_1%%";
58+
ssl_trusted_certificate acme.test.crt;
59+
state_path %%TESTDIR%%/acme_chain1;
60+
accept_terms_of_service;
61+
}
62+
63+
acme_issuer chain2 {
64+
uri https://acme.test:%%PORT_9000%%/dir;
65+
preferred_chain "%%ISSUER_NAME_2%%";
66+
ssl_trusted_certificate acme.test.crt;
67+
state_path %%TESTDIR%%/acme_chain2;
68+
accept_terms_of_service;
69+
}
70+
71+
ssl_certificate $acme_certificate;
72+
ssl_certificate_key $acme_certificate_key;
73+
74+
server {
75+
listen 127.0.0.1:8443 ssl;
76+
server_name example.test;
77+
78+
acme_certificate default;
79+
}
80+
81+
server {
82+
listen 127.0.0.1:8444 ssl;
83+
server_name example.test;
84+
85+
acme_certificate chain1;
86+
}
87+
88+
server {
89+
listen 127.0.0.1:8445 ssl;
90+
server_name example.test;
91+
92+
acme_certificate chain2;
93+
}
94+
95+
server {
96+
listen 127.0.0.1:8080;
97+
server_name example.test;
98+
}
99+
}
100+
101+
EOF
102+
103+
$t->write_file('openssl.conf', <<EOF);
104+
[ req ]
105+
default_bits = 2048
106+
encrypt_key = no
107+
distinguished_name = req_distinguished_name
108+
[ req_distinguished_name ]
109+
EOF
110+
111+
my $d = $t->testdir();
112+
113+
foreach my $name ('acme.test') {
114+
system('openssl req -x509 -new '
115+
. "-config $d/openssl.conf -subj /CN=$name/ "
116+
. "-out $d/$name.crt -keyout $d/$name.key "
117+
. ">>$d/openssl.out 2>&1") == 0
118+
or die "Can't create certificate for $name: $!\n";
119+
}
120+
121+
my $dp = port(8980, udp=>1);
122+
my @dc = (
123+
{ name => 'acme.test', A => '127.0.0.1' },
124+
{ name => 'example.test', A => '127.0.0.1' }
125+
);
126+
127+
my $acme = Test::Nginx::ACME->new($t, port(9000), port(9001),
128+
$t->testdir . '/acme.test.crt',
129+
$t->testdir . '/acme.test.key',
130+
alternate_roots => 2,
131+
http_port => port(8080),
132+
dns_port => $dp,
133+
nosleep => 1,
134+
);
135+
136+
$t->run_daemon(\&Test::Nginx::DNS::dns_test_daemon, $t, $dp, \@dc);
137+
$t->waitforfile($t->testdir . '/' . $dp);
138+
139+
$t->run_daemon(\&Test::Nginx::ACME::acme_test_daemon, $t, $acme);
140+
$t->waitforsocket('127.0.0.1:' . $acme->port());
141+
$t->write_file('acme-root-0.crt', $acme->trusted_ca(0));
142+
$t->write_file('acme-root-1.crt', $acme->trusted_ca(1));
143+
$t->write_file('acme-root-2.crt', $acme->trusted_ca(2));
144+
145+
# Pebble Root name is randomly generated
146+
147+
my $cn = cert_name($t->testdir . '/acme-root-1.crt')
148+
or die "Can't get CA certificate name: $!";
149+
$conf =~ s/%%ISSUER_NAME_1%%/$cn/;
150+
151+
$cn = cert_name($t->testdir . '/acme-root-2.crt')
152+
or die "Can't get CA certificate name: $!";
153+
$conf =~ s/%%ISSUER_NAME_2%%/$cn/;
154+
155+
$t->write_file_expand('nginx.conf', $conf);
156+
157+
$t->write_file('index.html', 'SUCCESS');
158+
$t->plan(5)->run();
159+
160+
###############################################################################
161+
162+
$acme->wait_certificate('acme_default/example.test') or die "no certificate";
163+
$acme->wait_certificate('acme_chain1/example.test') or die "no certificate";
164+
$acme->wait_certificate('acme_chain2/example.test') or die "no certificate";
165+
166+
like(get(8443, 'example.test', 'acme-root-0'), qr/SUCCESS/, 'default');
167+
168+
like(get(8444, 'example.test', 'acme-root-1'), qr/SUCCESS/, 'chain 1');
169+
is(get(8444, 'example.test', 'acme-root-0'), undef, 'chain 1 - wrong root');
170+
171+
like(get(8445, 'example.test', 'acme-root-2'), qr/SUCCESS/, 'chain 2');
172+
is(get(8445, 'example.test', 'acme-root-0'), undef, 'chain 2 - wrong root');
173+
174+
###############################################################################
175+
176+
sub get {
177+
my ($port, $host, $ca) = @_;
178+
179+
$ca = undef if $IO::Socket::SSL::VERSION < 2.062
180+
|| !eval { Net::SSLeay::X509_V_FLAG_PARTIAL_CHAIN() };
181+
182+
http_get('/',
183+
PeerAddr => '127.0.0.1:' . port($port),
184+
SSL => 1,
185+
$ca ? (
186+
SSL_ca_file => "$d/$ca.crt",
187+
SSL_verifycn_name => $host,
188+
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_PEER(),
189+
) : ()
190+
);
191+
}
192+
193+
sub cert_name {
194+
my ($filename) = @_;
195+
196+
my $bio = Net::SSLeay::BIO_new_file($filename, 'r')
197+
or die_now("BIO_new_file() failed: $!");
198+
199+
my $cert = Net::SSLeay::PEM_read_bio_X509($bio)
200+
or die_now("PEM_read_bio_X509() failed: $!");
201+
202+
my $name = Net::SSLeay::X509_get_subject_name($cert)
203+
or die_now("X509_get_subject_name() failed: $!");
204+
205+
return Net::SSLeay::X509_NAME_get_text_by_NID(
206+
$name,
207+
Net::SSLeay::NID_commonName()
208+
);
209+
}
210+
211+
###############################################################################

t/lib/Test/Nginx/ACME.pm

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ sub new {
4343
my $tls_port = $extra{tls_port} || 443;
4444
my $validity = $extra{validity} || 3600;
4545

46+
$self->{alternate_roots} = $extra{alternate_roots};
4647
$self->{dns_port} = $extra{dns_port} || Test::Nginx::port(8980, udp=>1);
4748
$self->{noncereject} = $extra{noncereject};
4849
$self->{nosleep} = $extra{nosleep};
@@ -84,10 +85,9 @@ sub port {
8485
}
8586

8687
sub trusted_ca {
87-
my $self = shift;
88+
my ($self, $chain) = @_;
8889
Test::Nginx::log_core('|| ACME: get certificate from', $self->{mgmt});
89-
my $cert = _get_body($self->{mgmt}, '/roots/0');
90-
$cert =~ s/(BEGIN|END) C/$1 TRUSTED C/g;
90+
my $cert = _get_body($self->{mgmt}, '/roots/' . ($chain // 0));
9191
$cert;
9292
}
9393

@@ -100,7 +100,7 @@ sub wait_certificate {
100100
my $timeout = ($extra{'timeout'} // 20) * 5;
101101

102102
for (1 .. $timeout) {
103-
return 1 if defined glob($file);
103+
return 1 if scalar @{[ glob $file ]};
104104
select undef, undef, undef, 0.2;
105105
}
106106
}
@@ -176,6 +176,8 @@ sub acme_test_daemon {
176176
my $port = $acme->{port};
177177
my $dnsserver = '127.0.0.1:' . $acme->{dns_port};
178178

179+
$ENV{PEBBLE_ALTERNATE_ROOTS} =
180+
$acme->{alternate_roots} if $acme->{alternate_roots};
179181
$ENV{PEBBLE_VA_NOSLEEP} = 1 if $acme->{nosleep};
180182
$ENV{PEBBLE_WFE_NONCEREJECT} =
181183
$acme->{noncereject} if $acme->{noncereject};

0 commit comments

Comments
 (0)