Skip to content

Commit ff1d23b

Browse files
authored
Merge pull request #402 from smallstep/carl/asn1-cps
Add a certificate policy example to the Templates docs
2 parents f080d14 + 43d6aa4 commit ff1d23b

File tree

1 file changed

+89
-38
lines changed

1 file changed

+89
-38
lines changed

step-ca/templates.mdx

Lines changed: 89 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: Configuring `step-ca` Templates
33
html_title: Configuring open source step-ca Templates
44
description: Learn how to configure step-ca Templates
5-
updated_at: March 26, 2025
5+
updated_at: April 03, 2025
66
---
77

88
People use private CAs for all sorts of things, in many different contexts:
@@ -130,7 +130,7 @@ The following snippet shows a provisioner with custom X.509 and SSH templates:
130130
In the example above, you are able to use the defined organizational unit as the variable `{{ .OrganizationalUnit }}`,
131131
for example in a template like:
132132

133-
```json
133+
```go
134134
{
135135
"subject": {
136136
"organizationalUnit": {{ toJson .OrganizationalUnit }},
@@ -146,7 +146,7 @@ The following snippet shows a provisioner with custom X.509 and SSH templates:
146146
string representation of a JSON object, or you encoded in Base64. For
147147
example:
148148

149-
```json
149+
```go
150150
{
151151
"x509": {
152152
"template": "{{ toJson .Insecure.CR }}",
@@ -156,7 +156,7 @@ The following snippet shows a provisioner with custom X.509 and SSH templates:
156156

157157
Or using Base64:
158158

159-
```json
159+
```go
160160
{
161161
"x509": {
162162
"template": "e3sgdG9Kc29uIC5JbnNlY3VyZS5DUiB9fQ==",
@@ -186,7 +186,7 @@ X.509 templates can be used in two places:
186186

187187
Here's what the default X.509 [leaf certificate template](https://github.com/smallstep/crypto/blob/162770cad29063385cb768b0191814e4c6a94e45/x509util/templates.go#L98) looks like:
188188

189-
```json
189+
```go
190190
{
191191
"subject": {{ toJson .Subject }},
192192
"sans": {{ toJson .SANs }},
@@ -319,15 +319,15 @@ before being (optionally) PEM-encoded with Base64.
319319

320320
Use these functions to populate custom certificate OID `extensions`:
321321

322-
```
322+
```go
323323
"extensions": [
324324
{"id": "1.2.3.4", "critical": false, "value": {{ asn1Enc "int:3848281" | toJson }}}
325325
]
326326
```
327327

328328
When applied to template variables, these functions enable dynamic OID extensions:
329329

330-
```
330+
```go
331331
{
332332
"subject": {{ toJson .Subject }},
333333
"sans": {{ toJson .SANs }},
@@ -347,7 +347,7 @@ When applied to template variables, these functions enable dynamic OID extension
347347

348348
Let's walk through one line of this template:
349349

350-
```
350+
```go
351351
{"id": "1.2.3.6", "value": {{ asn1Seq (asn1Enc "YubiKey") (asn1Enc "int:123456") | toJson }}}
352352
```
353353

@@ -365,7 +365,7 @@ Encodes a string into an ASN.1 value.
365365
A data type can be supplied as a prefix,
366366
and ASN.1 Printable is the default data type:
367367

368-
```
368+
```go
369369
asn1Enc "int:123"
370370
asn1Enc "oid:1.2.3.4"
371371
asn1Enc "foo"
@@ -395,7 +395,7 @@ without converting it to a string first, `asn1Marshal .AuthorizationCrt.NotAfter
395395

396396
`asn1Marshal` imitates Go's [`asn1.MarshalWithParams`](https://pkg.go.dev/encoding/asn1#MarshalWithParams) function.
397397

398-
```
398+
```go
399399
asn1Marshal .Token.iss
400400
asn1Marshal .AuthorizationCrt.NotAfter "utc"
401401
```
@@ -407,15 +407,15 @@ It will use Go's default transformation if no data type is supplied.
407407

408408
Creates an ASN.1 `SEQUENCE` of values.
409409

410-
```
410+
```go
411411
asn1Seq (asn1Enc "foo") (asn1Enc "int:123") ...
412412
```
413413

414414
#### asn1Set
415415

416416
Creates an ASN.1 `SET` of values.
417417

418-
```
418+
```go
419419
asn1Set (asn1Enc "foo") (asn1Enc "int:123") ...
420420
```
421421

@@ -424,7 +424,7 @@ asn1Set (asn1Enc "foo") (asn1Enc "int:123") ...
424424
`step-ca` also supports SSH certificate templates.
425425
Here is `step-ca`'s default [SSH certificate template](https://github.com/smallstep/crypto/blob/162770cad29063385cb768b0191814e4c6a94e45/sshutil/templates.go#L144):
426426

427-
```json
427+
```go
428428
{
429429
"type": {{ toJson .Type }},
430430
"keyId": {{ toJson .KeyID }},
@@ -454,7 +454,7 @@ Here are the most relevant parameters available in SSH certificate template:
454454
- **.Extensions**: is a map containing extensions.
455455
The default value for `Extensions` is:
456456

457-
```json
457+
```go
458458
{
459459
"permit-X11-forwarding": "",
460460
"permit-agent-forwarding": "",
@@ -530,7 +530,7 @@ use.
530530

531531
The default leaf certificate template is:
532532

533-
```json
533+
```go
534534
{
535535
"subject": {{ toJson .Subject }},
536536
"sans": {{ toJson .SANs }},
@@ -554,7 +554,7 @@ step ca certificate [email protected] jane.crt
554554
The rendered template (from which the X.509 certificate will be generated and
555555
signed) is:
556556

557-
```json
557+
```go
558558
{
559559
"subject": {
560560
"commonName": "[email protected]"
@@ -605,7 +605,7 @@ a root certificate.
605605

606606
The default template for an intermediate certificate is:
607607

608-
```json
608+
```go
609609
{
610610
"subject": {{ toJson .Subject }},
611611
"keyUsage": ["certSign", "crlSign"],
@@ -648,7 +648,7 @@ step certificate create --template intermediate.tpl \
648648
A root certificate is a self-signed certificate used to sign other certificates.
649649
The default root certificate template is:
650650

651-
```json
651+
```go
652652
{
653653
"subject": {{ toJson .Subject }},
654654
"issuer": {{ toJson .Subject }},
@@ -698,7 +698,7 @@ Below, we'll walk through a few advanced templating examples.
698698

699699
Let's start with one of the shortest templates:
700700

701-
```json
701+
```go
702702
{{ toJson .Insecure.CR }}
703703
```
704704

@@ -721,7 +721,7 @@ hardware, and you need to define a SAN for it.
721721
For example, below is an X.509 template that accepts the user-supplied value `dnsName`
722722
but it falls back to the default leaf template value if it's not present:
723723

724-
```json
724+
```go
725725
{
726726
"subject": {{ toJson .Subject }},
727727
{{- if .Insecure.User.dnsName }}
@@ -756,7 +756,7 @@ It's worth mentioning the while we used `"dnsNames"` instead of `"sans"` in the
756756
be used. `"dnsNames"` is a list of strings (or just one string if only one is
757757
required), while `"sans"` is an list of maps:
758758

759-
```json
759+
```go
760760
[
761761
{"type": "dns", "value": "backend.example.com"},
762762
{"type": "email", "value": "[email protected]"},
@@ -778,7 +778,7 @@ the validity of certificates that an intermediate can sign. If we want to only
778778
allow DNS name like \*.example.com we can generate an intermediate with the
779779
template:
780780

781-
```
781+
```go
782782
{
783783
"subject": {{ toJson .Subject }},
784784
"keyUsage": ["certSign", "crlSign"],
@@ -803,8 +803,9 @@ step certificate create --template intermediate.tpl --ca root_ca.crt --ca-key ro
803803
Besides `"permittedDNSDomains"`, the `"nameConstraints"` property accepts all
804804
the following properties:
805805

806-
```json
806+
```go
807807
{
808+
...
808809
"nameConstraints": {
809810
"critical": false,
810811
"permittedDNSDomains": ["doe.com"],
@@ -814,20 +815,23 @@ the following properties:
814815
"permittedEmailAddresses": ["[email protected]"],
815816
"excludedEmailAddresses": ["[email protected]"],
816817
"permittedURIDomains": ["https://doe.com"],
817-
"excludedURIDomains": ["https://doe.org"],
818-
}
818+
"excludedURIDomains": ["https://doe.org"]
819+
}
820+
...
819821
}
820822
```
821823

822824
Remember that in certificate templates, if an array only has one member, you can
823825
write it as a string:
824826

825-
```json
827+
```go
826828
{
829+
...
827830
"nameConstraints": {
828831
"critical": true,
829832
"permittedDNSDomains": "example.com"
830833
}
834+
...
831835
}
832836
```
833837

@@ -859,18 +863,65 @@ certificate policies [(RFC 5280, 4.2.1.4)](https://www.rfc-editor.org/rfc/rfc528
859863
But if you need to create your own extension, or use an unsupported one, you can
860864
also write a custom extension like:
861865

862-
```json
866+
```go
863867
{
864-
"extensions": [
865-
{"id": "1.2.3.4", "critical": false, "value": "Y3VzdG9tIGV4dGVuc2lvbiB2YWx1ZQ=="}
866-
]
868+
...
869+
"extensions": [
870+
{"id": "1.2.3.4", "critical": false, "value": "Y3VzdG9tIGV4dGVuc2lvbiB2YWx1ZQ=="}
871+
]
872+
...
873+
}
874+
```
875+
876+
The value of the extension is the Base64 encoding of the
877+
actual ASN.1 bytes that go into that extension.
878+
879+
For a more human-readable template,
880+
you can also use [ASN.1 functions](#asn1-values) in the extension `value` field.
881+
We'll do that in the next example.
882+
883+
#### Deep dive: Certificate Policies
884+
885+
X.509 Certificate Policies define policy constraints of a certificate.
886+
They help relying parties determine the trustworthiness of a certificate,
887+
and how to use it in practice.
888+
889+
For example, in the Web PKI, the Policy OID `2.23.140.1.2.1` is used to distinguish a certificate that meets the [CA/Browser Forum Baseline Requirements](https://cabforum.org/working-groups/server/baseline-requirements/requirements/) for Domain Validation.
890+
891+
In an internal PKI, a large organization might use a Certificate Policy to detail to a relying party how the CA verified the identity of the certificate requestor, or to approve a certificate for specific applications.
892+
But, this sort of use case is very niche.
893+
894+
A Certificate Practices Statement (CPS) is type of Certificate Policy that references a document describing a CA's operational practices and security controls.
895+
896+
Here's [an example of a CPS from Let's Encrypt](https://letsencrypt.org/documents/isrg-cp-cps-v5.7/).
897+
898+
Let's add a policy extension with a CPS to an X.509 template.
899+
We'll need to construct some ASN.1 for this.
900+
901+
```go
902+
{
903+
...
904+
"extensions": [
905+
{
906+
"id": "2.5.29.32",
907+
"value": {{
908+
asn1Seq
909+
(asn1Seq
910+
(asn1Enc "oid:1.3.6.1.4.1.99999.1.1.1")
911+
(asn1Seq (asn1Seq (asn1Enc "oid:1.3.6.1.5.5.7.2.1") (asn1Enc "ia5:http://example.com/cps")))
912+
)
913+
| toJson
914+
}}
915+
}
916+
]
917+
...
867918
}
868919
```
869920

870-
The crux here is that the value of the extension is the Base64 encoding of the
871-
actual bytes that go into that extension, so if you are encoding a structure
872-
in your extension using the ASN.1 encoding, you will have to put the Base64
873-
version of the encoded bytes.
921+
First, the public OID `2.5.29.32` represents the Certificate Policies extension.
922+
In the value for this extension, we have two policies.
923+
The first references Policy OID `1.3.6.1.4.1.99999.1.1.1`, a custom policy OID defined by our example organization.
924+
The second policy contains both the well-defined OID `1.3.6.1.5.5.7.2.1` [representing a CPS pointer](https://www.rfc-editor.org/rfc/rfc5280.html#section-4.2.1.4), and the CPS pointer `http://example.com/cps`.
874925

875926
#### X.509 OpenVPN certificates
876927

@@ -880,7 +931,7 @@ key usages not available in the default templates.
880931

881932
This is a template you can use in a provisioner signing OpenVPN client certificates:
882933

883-
```
934+
```go
884935
{
885936
"subject": {"commonName": {{ toJson .Insecure.CR.Subject.CommonName }}},
886937
"sans": {{ toJson .SANs }},
@@ -892,7 +943,7 @@ This is a template you can use in a provisioner signing OpenVPN client certifica
892943
And the following template can be used for a provisioner signing OpenVPN server
893944
certificates:
894945

895-
```
946+
```go
896947
{
897948
"subject": {{ toJson .Subject }},
898949
"sans": {{ toJson .SANs }},
@@ -919,7 +970,7 @@ Which is great: The same certificates that let you SSH into your servers now als
919970

920971
Here's an SSH template that supports the GitHub custom SSH certificate extension:
921972

922-
```bash
973+
```go
923974
{
924975
"type": {{ toJson .Type }},
925976
"keyId": {{ toJson .KeyID }},
@@ -1041,7 +1092,7 @@ First, configure a custom `groups` claim with your identity provider, and add th
10411092
In this example, we're assuming the `groups` claim contains a space-separated list of possible group accounts.
10421093

10431094
Then, use the following template to merge the group accounts with the user's own principals (derived from the `email` claim):
1044-
```json
1095+
```go
10451096
{
10461097
"type": {{ toJson .Type }},
10471098
"keyId": {{ toJson .KeyID }},

0 commit comments

Comments
 (0)