Skip to content

Commit 7b69ae9

Browse files
authored
Merge pull request #744 from smallstep/mariano/policies
Fix templates with policies with Go 1.24
2 parents 6120b57 + 67ff3ee commit 7b69ae9

File tree

5 files changed

+333
-43
lines changed

5 files changed

+333
-43
lines changed

x509util/certificate_test.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ import (
2626
"go.step.sm/crypto/pemutil"
2727
)
2828

29+
func mustOID(t *testing.T, s string) x509.OID {
30+
t.Helper()
31+
32+
oid, err := x509.ParseOID(s)
33+
require.NoError(t, err)
34+
return oid
35+
}
36+
2937
func createCertificateRequest(t *testing.T, commonName string, sans []string) (*x509.CertificateRequest, crypto.Signer) {
3038
dnsNames, ips, emails, uris := SplitSANs(sans)
3139
t.Helper()
@@ -258,7 +266,7 @@ func TestNewCertificate(t *testing.T) {
258266
OCSPServer: []string{"https://ocsp.server"},
259267
IssuingCertificateURL: []string{"https://ca.com"},
260268
CRLDistributionPoints: []string{"https://ca.com/ca.crl"},
261-
PolicyIdentifiers: PolicyIdentifiers{[]int{1, 2, 3, 4, 5, 6}},
269+
PolicyIdentifiers: PolicyIdentifiers{mustOID(t, "1.2.3.4.5.6")},
262270
BasicConstraints: &BasicConstraints{
263271
IsCA: false,
264272
MaxPathLen: 0,
@@ -612,7 +620,7 @@ func TestNewCertificateFromX509(t *testing.T) {
612620
OCSPServer: []string{"https://ocsp.server"},
613621
IssuingCertificateURL: []string{"https://ca.com"},
614622
CRLDistributionPoints: []string{"https://ca.com/ca.crl"},
615-
PolicyIdentifiers: PolicyIdentifiers{[]int{1, 2, 3, 4, 5, 6}},
623+
PolicyIdentifiers: PolicyIdentifiers{mustOID(t, "1.2.3.4.5.6")},
616624
BasicConstraints: &BasicConstraints{
617625
IsCA: false,
618626
MaxPathLen: 0,
@@ -715,7 +723,7 @@ func TestCertificate_GetCertificate(t *testing.T) {
715723
OCSPServer: []string{"https://oscp.server"},
716724
IssuingCertificateURL: []string{"https://ca.com"},
717725
CRLDistributionPoints: []string{"https://ca.com/crl"},
718-
PolicyIdentifiers: []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}},
726+
PolicyIdentifiers: PolicyIdentifiers{mustOID(t, "1.2.3.4")},
719727
BasicConstraints: &BasicConstraints{IsCA: true, MaxPathLen: 0},
720728
NameConstraints: &NameConstraints{PermittedDNSDomains: []string{"foo.bar"}},
721729
SignatureAlgorithm: SignatureAlgorithm(x509.PureEd25519),
@@ -744,7 +752,8 @@ func TestCertificate_GetCertificate(t *testing.T) {
744752
OCSPServer: []string{"https://oscp.server"},
745753
IssuingCertificateURL: []string{"https://ca.com"},
746754
CRLDistributionPoints: []string{"https://ca.com/crl"},
747-
PolicyIdentifiers: []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}},
755+
PolicyIdentifiers: []asn1.ObjectIdentifier{{1, 2, 3, 4}},
756+
Policies: []x509.OID{mustOID(t, "1.2.3.4")},
748757
IsCA: true,
749758
MaxPathLen: 0,
750759
MaxPathLenZero: true,
@@ -785,9 +794,7 @@ func TestCertificate_GetCertificate(t *testing.T) {
785794
PublicKeyAlgorithm: tt.fields.PublicKeyAlgorithm,
786795
PublicKey: tt.fields.PublicKey,
787796
}
788-
if got := c.GetCertificate(); !reflect.DeepEqual(got, tt.want) {
789-
t.Errorf("Certificate.GetCertificate() = %v, want %v", got, tt.want)
790-
}
797+
assert.Equal(t, tt.want, c.GetCertificate())
791798
})
792799
}
793800
}

x509util/extensions.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -890,26 +890,37 @@ func (u CRLDistributionPoints) Set(c *x509.Certificate) {
890890

891891
// PolicyIdentifiers represents the list of OIDs to set in the certificate
892892
// policies extension.
893-
type PolicyIdentifiers MultiObjectIdentifier
893+
type PolicyIdentifiers MultiOID
894894

895895
// MarshalJSON implements the json.Marshaler interface in PolicyIdentifiers.
896896
func (p PolicyIdentifiers) MarshalJSON() ([]byte, error) {
897-
return MultiObjectIdentifier(p).MarshalJSON()
897+
return MultiOID(p).MarshalJSON()
898898
}
899899

900900
// UnmarshalJSON implements the json.Unmarshaler interface in PolicyIdentifiers.
901901
func (p *PolicyIdentifiers) UnmarshalJSON(data []byte) error {
902-
var v MultiObjectIdentifier
902+
var v MultiOID
903903
if err := json.Unmarshal(data, &v); err != nil {
904904
return errors.Wrap(err, "error unmarshaling json")
905905
}
906906
*p = PolicyIdentifiers(v)
907907
return nil
908908
}
909909

910-
// Set sets the policy identifiers to the given certificate.
910+
// Set sets the policy identifiers to the given certificate. To ensure
911+
// compatibility between different versions of Go, set will set
912+
// PolicyIdentifiers and Policies with the same data.
913+
//
914+
// Programs using go.mod 1.24+ will only marshal the Policies field, older
915+
// versions will only marshal PolicyIdentifiers. This can be changed with the
916+
// GODEBUG setting "x509usepolicies".
911917
func (p PolicyIdentifiers) Set(c *x509.Certificate) {
912-
c.PolicyIdentifiers = p
918+
c.Policies = p
919+
for _, pp := range p {
920+
if oid, err := parseObjectIdentifier(pp.String()); err == nil {
921+
c.PolicyIdentifiers = append(c.PolicyIdentifiers, oid)
922+
}
923+
}
913924
}
914925

915926
// BasicConstraints represents the X509 basic constraints extension and defines

x509util/extensions_test.go

Lines changed: 206 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"crypto/x509/pkix"
77
"encoding/asn1"
88
"encoding/json"
9+
"fmt"
910
"math/big"
1011
"net"
1112
"net/url"
@@ -967,9 +968,7 @@ func TestCRLDistributionPoints_Set(t *testing.T) {
967968
for _, tt := range tests {
968969
t.Run(tt.name, func(t *testing.T) {
969970
tt.o.Set(tt.args.c)
970-
if !reflect.DeepEqual(tt.args.c, tt.want) {
971-
t.Errorf("CRLDistributionPoints.Set() = %v, want %v", tt.args.c, tt.want)
972-
}
971+
assert.Equal(t, tt.want, tt.args.c)
973972
})
974973
}
975974
}
@@ -981,8 +980,8 @@ func TestPolicyIdentifiers_MarshalJSON(t *testing.T) {
981980
want []byte
982981
wantErr bool
983982
}{
984-
{"ok", []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}, []int{5, 6, 7, 8, 9, 0}}, []byte(`["1.2.3.4","5.6.7.8.9.0"]`), false},
985-
{"empty", []asn1.ObjectIdentifier{}, []byte(`[]`), false},
983+
{"ok", []x509.OID{mustOID(t, "1.2.3.4"), mustOID(t, "1.3.5.7")}, []byte(`["1.2.3.4","1.3.5.7"]`), false},
984+
{"empty", []x509.OID{}, []byte(`[]`), false},
986985
{"nil", nil, []byte(`null`), false},
987986
}
988987
for _, tt := range tests {
@@ -1009,9 +1008,9 @@ func TestPolicyIdentifiers_UnmarshalJSON(t *testing.T) {
10091008
want PolicyIdentifiers
10101009
wantErr bool
10111010
}{
1012-
{"string", args{[]byte(`"1.2.3.4"`)}, []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}}, false},
1013-
{"array", args{[]byte(`["1.2.3.4", "5.6.7.8.9.0"]`)}, []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}, []int{5, 6, 7, 8, 9, 0}}, false},
1014-
{"empty", args{[]byte(`[]`)}, []asn1.ObjectIdentifier{}, false},
1011+
{"string", args{[]byte(`"1.2.3.4"`)}, []x509.OID{mustOID(t, "1.2.3.4")}, false},
1012+
{"array", args{[]byte(`["1.2.3.4", "1.3.5.7"]`)}, []x509.OID{mustOID(t, "1.2.3.4"), mustOID(t, "1.3.5.7")}, false},
1013+
{"empty", args{[]byte(`[]`)}, []x509.OID{}, false},
10151014
{"null", args{[]byte(`null`)}, nil, false},
10161015
{"fail", args{[]byte(`":foo:bar"`)}, nil, true},
10171016
{"failJSON", args{[]byte(`["https://iss#sub"`)}, nil, true},
@@ -1029,29 +1028,6 @@ func TestPolicyIdentifiers_UnmarshalJSON(t *testing.T) {
10291028
}
10301029
}
10311030

1032-
func TestPolicyIdentifiers_Set(t *testing.T) {
1033-
type args struct {
1034-
c *x509.Certificate
1035-
}
1036-
tests := []struct {
1037-
name string
1038-
o PolicyIdentifiers
1039-
args args
1040-
want *x509.Certificate
1041-
}{
1042-
{"ok", []asn1.ObjectIdentifier{{1, 2, 3, 4}}, args{&x509.Certificate{}}, &x509.Certificate{PolicyIdentifiers: []asn1.ObjectIdentifier{{1, 2, 3, 4}}}},
1043-
{"overwrite", []asn1.ObjectIdentifier{{1, 2, 3, 4}, {4, 3, 2, 1}}, args{&x509.Certificate{PolicyIdentifiers: []asn1.ObjectIdentifier{{1, 2, 3, 4}}}}, &x509.Certificate{PolicyIdentifiers: []asn1.ObjectIdentifier{{1, 2, 3, 4}, {4, 3, 2, 1}}}},
1044-
}
1045-
for _, tt := range tests {
1046-
t.Run(tt.name, func(t *testing.T) {
1047-
tt.o.Set(tt.args.c)
1048-
if !reflect.DeepEqual(tt.args.c, tt.want) {
1049-
t.Errorf("PolicyIdentifiers.Set() = %v, want %v", tt.args.c, tt.want)
1050-
}
1051-
})
1052-
}
1053-
}
1054-
10551031
func TestBasicConstraints_Set(t *testing.T) {
10561032
type fields struct {
10571033
IsCA bool
@@ -1490,3 +1466,202 @@ func TestParseSubjectAlternativeNames(t *testing.T) {
14901466
})
14911467
}
14921468
}
1469+
1470+
func TestPolicyIdentifiers(t *testing.T) {
1471+
iss, issPriv := createIssuerCertificate(t, "issuer")
1472+
csr, _ := createCertificateRequest(t, "", []string{})
1473+
1474+
buildWithExtension := func(s string) string {
1475+
return fmt.Sprintf(`{
1476+
"subject": {{ toJson .Subject }},
1477+
"sans": {{ toJson .SANs }},
1478+
{{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}
1479+
"keyUsage": ["keyEncipherment", "digitalSignature"],
1480+
{{- else }}
1481+
"keyUsage": ["digitalSignature"],
1482+
{{- end }}
1483+
"extKeyUsage": ["serverAuth", "clientAuth"],
1484+
"extensions": [%s]
1485+
}`, s)
1486+
}
1487+
1488+
assertValue := func(t *testing.T, want []byte, crt *x509.Certificate) {
1489+
t.Helper()
1490+
for _, ext := range crt.Extensions {
1491+
if ext.Id.Equal(asn1.ObjectIdentifier{2, 5, 29, 32}) {
1492+
assert.Equal(t, want, ext.Value)
1493+
return
1494+
}
1495+
}
1496+
t.Error("extension not found")
1497+
}
1498+
1499+
var (
1500+
policyIdentifiersTemplate = `{
1501+
"subject": {{ toJson .Subject }},
1502+
"sans": {{ toJson .SANs }},
1503+
{{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}
1504+
"keyUsage": ["keyEncipherment", "digitalSignature"],
1505+
{{- else }}
1506+
"keyUsage": ["digitalSignature"],
1507+
{{- end }}
1508+
"extKeyUsage": ["serverAuth", "clientAuth"],
1509+
"policyIdentifiers": ["1.2.3.4", "1.3.5.7"]
1510+
}`
1511+
data = CreateTemplateData("commonName", []string{"test.example.com"})
1512+
)
1513+
1514+
tests := []struct {
1515+
name string
1516+
csr *x509.CertificateRequest
1517+
options []Option
1518+
assert func(t *testing.T, crt *x509.Certificate)
1519+
}{
1520+
{"no policies", csr, []Option{WithTemplate(DefaultLeafTemplate, data)}, func(t *testing.T, crt *x509.Certificate) {
1521+
assert.Equal(t, "commonName", crt.Subject.CommonName)
1522+
assert.Equal(t, []string{"test.example.com"}, crt.DNSNames)
1523+
assert.Empty(t, crt.PolicyIdentifiers)
1524+
assert.Empty(t, crt.Policies)
1525+
}},
1526+
{"policyIdentifiers", csr, []Option{WithTemplate(policyIdentifiersTemplate, data)}, func(t *testing.T, crt *x509.Certificate) {
1527+
assert.Equal(t, []asn1.ObjectIdentifier{{1, 2, 3, 4}, {1, 3, 5, 7}}, crt.PolicyIdentifiers)
1528+
assert.Equal(t, []x509.OID{mustOID(t, "1.2.3.4"), mustOID(t, "1.3.5.7")}, crt.Policies)
1529+
assertValue(t, []byte{
1530+
0x30, 0x0E, 0x30, 0x05, 0x06, 0x03, 0x2A, 0x03,
1531+
0x04, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x05, 0x07,
1532+
}, crt)
1533+
}},
1534+
{"extension", csr, []Option{WithTemplate(buildWithExtension(`{
1535+
"id": "2.5.29.32",
1536+
"value": {{ asn1Seq (asn1Seq (asn1Enc "oid:1.2.3.4")) (asn1Seq (asn1Enc "oid:1.3.5.7")) | toJson }}
1537+
}`), data)}, func(t *testing.T, crt *x509.Certificate) {
1538+
assert.Equal(t, []asn1.ObjectIdentifier{{1, 2, 3, 4}, {1, 3, 5, 7}}, crt.PolicyIdentifiers)
1539+
assert.Equal(t, []x509.OID{mustOID(t, "1.2.3.4"), mustOID(t, "1.3.5.7")}, crt.Policies)
1540+
assertValue(t, []byte{
1541+
0x30, 0x0E, 0x30, 0x05, 0x06, 0x03, 0x2A, 0x03,
1542+
0x04, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x05, 0x07,
1543+
}, crt)
1544+
}},
1545+
{"withCPS", csr, []Option{WithTemplate(buildWithExtension(`{
1546+
"id": "2.5.29.32",
1547+
"value": {{
1548+
asn1Seq
1549+
(asn1Seq
1550+
(asn1Enc "oid:1.3.5.7")
1551+
(asn1Seq (asn1Seq (asn1Enc "oid:1.3.6.1.5.5.7.2.1") (asn1Enc "ia5:http://example.com/cps")))
1552+
)
1553+
| toJson
1554+
}}
1555+
}`), data)}, func(t *testing.T, crt *x509.Certificate) {
1556+
assert.Equal(t, []asn1.ObjectIdentifier{{1, 3, 5, 7}}, crt.PolicyIdentifiers)
1557+
assert.Equal(t, []x509.OID{mustOID(t, "1.3.5.7")}, crt.Policies)
1558+
assertValue(t, []byte{
1559+
0x30, 0x2D, 0x30, 0x2B, 0x06, 0x03, 0x2B, 0x05,
1560+
0x07, 0x30, 0x24, 0x30, 0x22, 0x06, 0x08, 0x2B,
1561+
0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16,
1562+
0x16, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F,
1563+
0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E,
1564+
0x63, 0x6F, 0x6D, 0x2F, 0x63, 0x70, 0x73,
1565+
}, crt)
1566+
}},
1567+
{"withUserNotice", csr, []Option{WithTemplate(buildWithExtension(`{
1568+
"id": "2.5.29.32",
1569+
"value": {{
1570+
asn1Seq
1571+
(asn1Seq
1572+
(asn1Enc "oid:1.3.5.7")
1573+
(asn1Seq
1574+
(asn1Seq
1575+
(asn1Enc "oid:1.3.6.1.5.5.7.2.2")
1576+
(asn1Seq
1577+
(asn1Seq
1578+
(asn1Enc "ia5:ACME Inc.")
1579+
(asn1Seq (asn1Enc "int:1") (asn1Enc "int:2") (asn1Enc "int:3") (asn1Enc "int:4"))
1580+
)
1581+
(asn1Enc "ia5:The explicit text.")
1582+
)
1583+
)
1584+
)
1585+
)
1586+
| toJson
1587+
}}
1588+
}`), data)}, func(t *testing.T, crt *x509.Certificate) {
1589+
assert.Equal(t, []asn1.ObjectIdentifier{{1, 3, 5, 7}}, crt.PolicyIdentifiers)
1590+
assert.Equal(t, []x509.OID{mustOID(t, "1.3.5.7")}, crt.Policies)
1591+
assertValue(t, []byte{
1592+
0x30, 0x46, 0x30, 0x44, 0x06, 0x03, 0x2B, 0x05,
1593+
0x07, 0x30, 0x3D, 0x30, 0x3B, 0x06, 0x08, 0x2B,
1594+
0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x02, 0x30,
1595+
0x2F, 0x30, 0x19, 0x16, 0x09, 0x41, 0x43, 0x4D,
1596+
0x45, 0x20, 0x49, 0x6E, 0x63, 0x2E, 0x30, 0x0C,
1597+
0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01,
1598+
0x03, 0x02, 0x01, 0x04, 0x16, 0x12, 0x54, 0x68,
1599+
0x65, 0x20, 0x65, 0x78, 0x70, 0x6C, 0x69, 0x63,
1600+
0x69, 0x74, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2E,
1601+
}, crt)
1602+
}},
1603+
{"complex", csr, []Option{WithTemplate(buildWithExtension(`{
1604+
"id": "2.5.29.32",
1605+
"value": {{
1606+
asn1Seq
1607+
(asn1Seq (asn1Enc "oid:1.2.3.4"))
1608+
(asn1Seq (asn1Enc "oid:1.3.5.7"))
1609+
(asn1Seq
1610+
(asn1Enc "oid:1.3.5.8")
1611+
(asn1Seq
1612+
(asn1Seq (asn1Enc "oid:1.3.6.1.5.5.7.2.1") (asn1Enc "ia5:http://example.com/cps"))
1613+
(asn1Seq (asn1Enc "oid:1.3.6.1.5.5.7.2.1") (asn1Enc "ia5:http://example.org/1.0/CPS"))
1614+
(asn1Seq
1615+
(asn1Enc "oid:1.3.6.1.5.5.7.2.2")
1616+
(asn1Seq
1617+
(asn1Seq
1618+
(asn1Enc "ia5:Organization Name")
1619+
(asn1Seq (asn1Enc "int:1") (asn1Enc "int:2") (asn1Enc "int:3") (asn1Enc "int:4"))
1620+
)
1621+
(asn1Enc "ia5:Explicit Text")
1622+
)
1623+
)
1624+
)
1625+
)
1626+
| toJson
1627+
}}
1628+
}`), data)}, func(t *testing.T, crt *x509.Certificate) {
1629+
assert.Equal(t, []asn1.ObjectIdentifier{{1, 2, 3, 4}, {1, 3, 5, 7}, {1, 3, 5, 8}}, crt.PolicyIdentifiers)
1630+
assert.Equal(t, []x509.OID{mustOID(t, "1.2.3.4"), mustOID(t, "1.3.5.7"), mustOID(t, "1.3.5.8")}, crt.Policies)
1631+
assertValue(t, []byte{
1632+
0x30, 0x81, 0xA5, 0x30, 0x05, 0x06, 0x03, 0x2A,
1633+
0x03, 0x04, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x05,
1634+
0x07, 0x30, 0x81, 0x94, 0x06, 0x03, 0x2B, 0x05,
1635+
0x08, 0x30, 0x81, 0x8C, 0x30, 0x22, 0x06, 0x08,
1636+
0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01,
1637+
0x16, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F,
1638+
0x2F, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65,
1639+
0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x63, 0x70, 0x73,
1640+
0x30, 0x26, 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05,
1641+
0x05, 0x07, 0x02, 0x01, 0x16, 0x1A, 0x68, 0x74,
1642+
0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x65, 0x78, 0x61,
1643+
0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x6F, 0x72, 0x67,
1644+
0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x43, 0x50, 0x53,
1645+
0x30, 0x3E, 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05,
1646+
0x05, 0x07, 0x02, 0x02, 0x30, 0x32, 0x30, 0x21,
1647+
0x16, 0x11, 0x4F, 0x72, 0x67, 0x61, 0x6E, 0x69,
1648+
0x7A, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x4E,
1649+
0x61, 0x6D, 0x65, 0x30, 0x0C, 0x02, 0x01, 0x01,
1650+
0x02, 0x01, 0x02, 0x02, 0x01, 0x03, 0x02, 0x01,
1651+
0x04, 0x16, 0x0D, 0x45, 0x78, 0x70, 0x6C, 0x69,
1652+
0x63, 0x69, 0x74, 0x20, 0x54, 0x65, 0x78, 0x74,
1653+
}, crt)
1654+
}},
1655+
}
1656+
for _, tt := range tests {
1657+
t.Run(tt.name, func(t *testing.T) {
1658+
cert, err := NewCertificate(tt.csr, tt.options...)
1659+
require.NoError(t, err)
1660+
1661+
template := cert.GetCertificate()
1662+
crt, err := CreateCertificate(template, iss, csr.PublicKey, issPriv)
1663+
require.NoError(t, err)
1664+
tt.assert(t, crt)
1665+
})
1666+
}
1667+
}

0 commit comments

Comments
 (0)