Skip to content

Commit 0f98660

Browse files
oncillakatyatitkovaromshark
authored
cmd/scion-pki: add certificate template command (scionproto#4815)
Add a `scion-pki certificate template` command that extracts the subject template from an existing certificate or CSR. This command can be used to reconstruct the subject template used to create a certificate or CSR. --------- Co-authored-by: Katya Titkova <katyatitkova@gmail.com> Co-authored-by: Roman Sharkov <roman.scharkov@gmail.com>
1 parent 4b3bef0 commit 0f98660

File tree

6 files changed

+161
-5
lines changed

6 files changed

+161
-5
lines changed

doc/command/scion-pki/scion-pki_certificate.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ SEE ALSO
3030
* :ref:`scion-pki certificate match <scion-pki_certificate_match>` - Match the certificate with other trust objects
3131
* :ref:`scion-pki certificate renew <scion-pki_certificate_renew>` - Renew an AS certificate
3232
* :ref:`scion-pki certificate sign <scion-pki_certificate_sign>` - Sign a certificate based on a certificate signing request
33+
* :ref:`scion-pki certificate template <scion-pki_certificate_template>` - Create subject template from a certificate or CSR
3334
* :ref:`scion-pki certificate validate <scion-pki_certificate_validate>` - Validate a SCION cert according to its type
3435
* :ref:`scion-pki certificate verify <scion-pki_certificate_verify>` - Verify a certificate chain
3536

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
:orphan:
2+
3+
.. _scion-pki_certificate_template:
4+
5+
scion-pki certificate template
6+
------------------------------
7+
8+
Create subject template from a certificate or CSR
9+
10+
Synopsis
11+
~~~~~~~~
12+
13+
14+
'template' creates a subject template from a certificate, certificate chain, or CSR.
15+
16+
This command allows reconstructing the subject template that was used to create a certificate
17+
or a certificate signing request (CSR). It is not necessary to use this command to create a
18+
new certificate or CSR, as the template to the 'create' command can also be a certificate
19+
itself.
20+
21+
In case the input is a certificate chain, the template is created from the first certificate
22+
in the chain.
23+
24+
25+
::
26+
27+
scion-pki certificate template [flags]
28+
29+
Options
30+
~~~~~~~
31+
32+
::
33+
34+
-h, --help help for template
35+
36+
SEE ALSO
37+
~~~~~~~~
38+
39+
* :ref:`scion-pki certificate <scion-pki_certificate>` - Manage certificates for the SCION control plane PKI.
40+

scion-pki/certs/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ go_library(
1414
"observability.go",
1515
"renew.go",
1616
"sign.go",
17+
"template.go",
1718
"validate.go",
1819
"verify.go",
1920
],

scion-pki/certs/certs.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func Cmd(pather command.Pather) *cobra.Command {
5858
newSignCmd(joined),
5959
newFingerprintCmd(joined),
6060
newInspectCmd(joined),
61+
newTemplateCmd(joined),
6162
)
6263
return cmd
6364
}

scion-pki/certs/create.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -430,11 +430,9 @@ func loadSubject(tmpl string) (pkix.Name, error) {
430430
if err != nil {
431431
return pkix.Name{}, err
432432
}
433-
// Check if template is a x509 certificate.
434-
cert, err := parseCertificate(raw)
435-
if err == nil {
436-
s := cert.Subject
437-
for _, name := range cert.Subject.Names {
433+
if subject, err := loadPkixNameFromRaw(raw); err == nil {
434+
s := subject
435+
for _, name := range subject.Names {
438436
// Ignore common name.
439437
if name.Type.Equal(asn1.ObjectIdentifier{2, 5, 4, 3}) {
440438
continue

scion-pki/certs/template.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright 2025 Anapaya Systems
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package certs
16+
17+
import (
18+
"crypto/x509"
19+
"crypto/x509/pkix"
20+
"encoding/json"
21+
"encoding/pem"
22+
"fmt"
23+
"os"
24+
25+
"github.com/spf13/cobra"
26+
27+
"github.com/scionproto/scion/pkg/scrypto/cppki"
28+
"github.com/scionproto/scion/private/app/command"
29+
)
30+
31+
func newTemplateCmd(_ command.Pather) *cobra.Command {
32+
cmd := &cobra.Command{
33+
Use: "template",
34+
Short: "Create subject template from a certificate or CSR",
35+
Long: `'template' creates a subject template from a certificate, certificate chain, or CSR.
36+
37+
This command allows reconstructing the subject template that was used to create a certificate
38+
or a certificate signing request (CSR). It is not necessary to use this command to create a
39+
new certificate or CSR, as the template to the 'create' command can also be a certificate
40+
itself.
41+
42+
In case the input is a certificate chain, the template is created from the first certificate
43+
in the chain.
44+
`,
45+
Args: cobra.ExactArgs(1),
46+
RunE: func(cmd *cobra.Command, args []string) error {
47+
subject, err := loadPkixNameFromFile(args[0])
48+
if err != nil {
49+
return fmt.Errorf("loading subject from file %q: %w", args[0], err)
50+
}
51+
52+
ia, err := cppki.ExtractIA(subject)
53+
if err != nil {
54+
return fmt.Errorf("extracting ISD-AS from certificate: %w", err)
55+
}
56+
57+
maybe := func(v []string) string {
58+
if len(v) == 0 {
59+
return ""
60+
}
61+
return v[0]
62+
}
63+
vars := SubjectVars{
64+
IA: ia,
65+
CommonName: subject.CommonName,
66+
Organization: maybe(subject.Organization),
67+
Country: maybe(subject.Country),
68+
Province: maybe(subject.Province),
69+
Locality: maybe(subject.Locality),
70+
OrganizationalUnit: maybe(subject.OrganizationalUnit),
71+
PostalCode: maybe(subject.PostalCode),
72+
StreetAddress: maybe(subject.StreetAddress),
73+
SerialNumber: subject.SerialNumber,
74+
}
75+
enc := json.NewEncoder(cmd.OutOrStdout())
76+
enc.SetIndent("", " ")
77+
if err := enc.Encode(vars); err != nil {
78+
return fmt.Errorf("encoding template: %w", err)
79+
}
80+
return nil
81+
},
82+
}
83+
return cmd
84+
}
85+
86+
func loadPkixNameFromFile(filename string) (pkix.Name, error) {
87+
raw, err := os.ReadFile(filename)
88+
if err != nil {
89+
return pkix.Name{}, fmt.Errorf("reading file %q: %w", filename, err)
90+
}
91+
return loadPkixNameFromRaw(raw)
92+
}
93+
94+
func loadPkixNameFromRaw(raw []byte) (pkix.Name, error) {
95+
pemData, _ := pem.Decode(raw)
96+
if pemData == nil {
97+
return pkix.Name{}, fmt.Errorf("not valid PEM")
98+
}
99+
switch pemData.Type {
100+
case "CERTIFICATE":
101+
cert, err := x509.ParseCertificate(pemData.Bytes)
102+
if err != nil {
103+
return pkix.Name{}, fmt.Errorf("parsing certificate: %w", err)
104+
}
105+
return cert.Subject, nil
106+
case "CERTIFICATE REQUEST":
107+
csr, err := x509.ParseCertificateRequest(pemData.Bytes)
108+
if err != nil {
109+
return pkix.Name{}, fmt.Errorf("parsing CSR: %w", err)
110+
}
111+
return csr.Subject, nil
112+
default:
113+
return pkix.Name{}, fmt.Errorf("invalid PEM block type %q", pemData.Type)
114+
}
115+
}

0 commit comments

Comments
 (0)