Skip to content

Commit 813fb0b

Browse files
committed
feat: check order identifiers difference between client and server
1 parent c56d454 commit 813fb0b

File tree

3 files changed

+153
-0
lines changed

3 files changed

+153
-0
lines changed

acme/api/identifier.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package api
2+
3+
import (
4+
"cmp"
5+
"slices"
6+
7+
"github.com/go-acme/lego/v4/acme"
8+
)
9+
10+
// compareIdentifiers compares 2 slices of [acme.Identifier].
11+
func compareIdentifiers(a, b []acme.Identifier) int {
12+
// Clones slices to avoid modifying original slices.
13+
right := slices.Clone(a)
14+
left := slices.Clone(b)
15+
16+
slices.SortStableFunc(right, compareIdentifier)
17+
slices.SortStableFunc(left, compareIdentifier)
18+
19+
return slices.CompareFunc(right, left, compareIdentifier)
20+
}
21+
22+
func compareIdentifier(right, left acme.Identifier) int {
23+
return cmp.Or(
24+
cmp.Compare(right.Type, left.Type),
25+
cmp.Compare(right.Value, left.Value),
26+
)
27+
}

acme/api/identifier_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package api
2+
3+
import (
4+
"testing"
5+
6+
"github.com/go-acme/lego/v4/acme"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func Test_compareIdentifiers(t *testing.T) {
11+
testCases := []struct {
12+
desc string
13+
a, b []acme.Identifier
14+
expected int
15+
}{
16+
{
17+
desc: "identical identifiers",
18+
a: []acme.Identifier{
19+
{Type: "dns", Value: "example.com"},
20+
{Type: "dns", Value: "*.example.com"},
21+
},
22+
b: []acme.Identifier{
23+
{Type: "dns", Value: "example.com"},
24+
{Type: "dns", Value: "*.example.com"},
25+
},
26+
expected: 0,
27+
},
28+
{
29+
desc: "identical identifiers but different order",
30+
a: []acme.Identifier{
31+
{Type: "dns", Value: "example.com"},
32+
{Type: "dns", Value: "*.example.com"},
33+
},
34+
b: []acme.Identifier{
35+
{Type: "dns", Value: "*.example.com"},
36+
{Type: "dns", Value: "example.com"},
37+
},
38+
expected: 0,
39+
},
40+
{
41+
desc: "duplicate identifiers",
42+
a: []acme.Identifier{
43+
{Type: "dns", Value: "example.com"},
44+
{Type: "dns", Value: "*.example.com"},
45+
},
46+
b: []acme.Identifier{
47+
{Type: "dns", Value: "example.com"},
48+
{Type: "dns", Value: "example.com"},
49+
},
50+
expected: -1,
51+
},
52+
{
53+
desc: "different identifier values",
54+
a: []acme.Identifier{
55+
{Type: "dns", Value: "example.com"},
56+
{Type: "dns", Value: "*.example.com"},
57+
},
58+
b: []acme.Identifier{
59+
{Type: "dns", Value: "example.com"},
60+
{Type: "dns", Value: "*.example.org"},
61+
},
62+
expected: -1,
63+
},
64+
{
65+
desc: "different identifier types",
66+
a: []acme.Identifier{
67+
{Type: "dns", Value: "example.com"},
68+
{Type: "dns", Value: "*.example.com"},
69+
},
70+
b: []acme.Identifier{
71+
{Type: "dns", Value: "example.com"},
72+
{Type: "ip", Value: "*.example.com"},
73+
},
74+
expected: -1,
75+
},
76+
{
77+
desc: "different number of identifiers a>b",
78+
a: []acme.Identifier{
79+
{Type: "dns", Value: "example.com"},
80+
{Type: "dns", Value: "*.example.com"},
81+
{Type: "dns", Value: "example.org"},
82+
},
83+
b: []acme.Identifier{
84+
{Type: "dns", Value: "example.com"},
85+
{Type: "dns", Value: "*.example.com"},
86+
},
87+
expected: 1,
88+
},
89+
{
90+
desc: "different number of identifiers b>a",
91+
a: []acme.Identifier{
92+
{Type: "dns", Value: "example.com"},
93+
{Type: "dns", Value: "*.example.com"},
94+
},
95+
b: []acme.Identifier{
96+
{Type: "dns", Value: "example.com"},
97+
{Type: "dns", Value: "*.example.com"},
98+
{Type: "dns", Value: "example.org"},
99+
},
100+
expected: -1,
101+
},
102+
}
103+
104+
for _, test := range testCases {
105+
t.Run(test.desc, func(t *testing.T) {
106+
t.Parallel()
107+
108+
assert.Equal(t, test.expected, compareIdentifiers(test.a, test.b))
109+
})
110+
}
111+
}

acme/api/order.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"net"
8+
"slices"
89
"time"
910

1011
"github.com/go-acme/lego/v4/acme"
@@ -85,6 +86,20 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm
8586
}
8687
}
8788

89+
// The elements of the "authorizations" and "identifiers" arrays are immutable once set.
90+
// The server MUST NOT change the contents of either array after they are created.
91+
// If a client observes a change in the contents of either array, then it SHOULD consider the order invalid.
92+
// https://www.rfc-editor.org/rfc/rfc8555#section-7.1.3
93+
if compareIdentifiers(orderReq.Identifiers, order.Identifiers) != 0 {
94+
// Sorts identifiers to avoid error message ambiguities about the order of the identifiers.
95+
slices.SortStableFunc(orderReq.Identifiers, compareIdentifier)
96+
slices.SortStableFunc(order.Identifiers, compareIdentifier)
97+
98+
return acme.ExtendedOrder{},
99+
fmt.Errorf("order identifiers have been by the ACME server (RFC8555 §7.1.3): %+v != %+v",
100+
orderReq.Identifiers, order.Identifiers)
101+
}
102+
88103
return acme.ExtendedOrder{
89104
Order: order,
90105
Location: resp.Header.Get("Location"),

0 commit comments

Comments
 (0)