Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions doc/command/scion-pki/scion-pki_trc_extract_certificates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ Options

::

-h, --help help for certificates
-o, --out string Output file (required)
-h, --help help for certificates
-o, --out string Output file (optional)
--subject.isd-as strings Filter certificates by ISD-AS of the subject (e.g., 1-ff00:0:110)
--type strings Filter certificates by type (any|cp-as|cp-ca|cp-root|regular-voting|sensitive-voting)

SEE ALSO
~~~~~~~~
Expand Down
10 changes: 10 additions & 0 deletions private/app/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package app

import (
"context"
"io"
"os"
"os/signal"
"syscall"
Expand Down Expand Up @@ -106,3 +107,12 @@ func (c *Cleanup) Do() error {
}
return errs.ToError()
}

// ReadFileOrStdin reads the content of a file or stdin if the path is "-".
// It returns the content as a byte slice.
func ReadFileOrStdin(path string) ([]byte, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open for suggestions if there is a more suitable place to put this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's fine to keep it here.

Maybe worth adding:

// ReadFileOrStdin reads the content of a file or stdin if the path is "-".
// It returns the content as a byte slice.
// Follows UNIX utility syntax guidelines.
// See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02

if path == "-" {
return io.ReadAll(os.Stdin)
}
return os.ReadFile(path)
}
15 changes: 14 additions & 1 deletion scion-pki/certs/fingerprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,29 @@ package certs

import (
"crypto/sha256"
"crypto/x509"
"fmt"

"github.com/spf13/cobra"

"github.com/scionproto/scion/pkg/private/serrors"
"github.com/scionproto/scion/pkg/scrypto/cppki"
"github.com/scionproto/scion/private/app"
"github.com/scionproto/scion/private/app/command"
"github.com/scionproto/scion/scion-pki/encoding"
)

func ReadPEMCerts(file string) ([]*x509.Certificate, error) {
raw, err := app.ReadFileOrStdin(file)
if err != nil {
return nil, err
}
if len(raw) == 0 {
return nil, fmt.Errorf("file %q is empty", file)
}
return cppki.ParsePEMCerts(raw)
}

func newFingerprintCmd(pather command.Pather) *cobra.Command {
var flags struct {
format string
Expand Down Expand Up @@ -53,7 +66,7 @@ If the flag \--format is set to "emoji", the format of the output is a string of
}
cmd.SilenceUsage = true

chain, err := cppki.ReadPEMCerts(args[0])
chain, err := ReadPEMCerts(args[0])
if err != nil {
return serrors.Wrap("loading certificate chain", err)
}
Expand Down
4 changes: 2 additions & 2 deletions scion-pki/certs/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import (
"encoding/pem"
"fmt"
"io"
"os"

"github.com/spf13/cobra"

"github.com/scionproto/scion/pkg/private/serrors"
"github.com/scionproto/scion/pkg/scrypto/cppki"
"github.com/scionproto/scion/private/app"
"github.com/scionproto/scion/private/app/command"
)

Expand All @@ -46,7 +46,7 @@ request (CSR) in human readable format.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
raw, err := os.ReadFile(args[0])
raw, err := app.ReadFileOrStdin(args[0])
if err != nil {
return serrors.Wrap("loading file", err)
}
Expand Down
3 changes: 2 additions & 1 deletion scion-pki/certs/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/scionproto/scion/pkg/private/serrors"
"github.com/scionproto/scion/pkg/scrypto/cppki"
"github.com/scionproto/scion/private/app"
"github.com/scionproto/scion/private/app/command"
"github.com/scionproto/scion/private/app/flag"
scionpki "github.com/scionproto/scion/scion-pki"
Expand Down Expand Up @@ -96,7 +97,7 @@ and not to \--not-before.

cmd.SilenceUsage = true

csrRaw, err := os.ReadFile(args[0])
csrRaw, err := app.ReadFileOrStdin(args[0])
if err != nil {
return serrors.Wrap("loading CSR", err)
}
Expand Down
2 changes: 1 addition & 1 deletion scion-pki/certs/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ period. This can be enabled by specifying the \--check-time flag.
cmd.SilenceUsage = true

filename := args[0]
certs, err := cppki.ReadPEMCerts(filename)
certs, err := ReadPEMCerts(filename)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions scion-pki/certs/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ the expected ISD-AS value.
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
chain, err := cppki.ReadPEMCerts(args[0])
chain, err := ReadPEMCerts(args[0])
if err != nil {
return serrors.Wrap("reading chain", err, "file", args[0])
}
Expand Down Expand Up @@ -148,7 +148,7 @@ The CA certificate must be a PEM encoded.
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
certs, err := cppki.ReadPEMCerts(args[0])
certs, err := ReadPEMCerts(args[0])
if err != nil {
return serrors.Wrap("reading certificate", err, "file", args[0])
}
Expand Down
1 change: 1 addition & 0 deletions scion-pki/key/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ go_library(
"//pkg/private/serrors:go_default_library",
"//pkg/scrypto:go_default_library",
"//pkg/scrypto/cppki:go_default_library",
"//private/app:go_default_library",
"//private/app/command:go_default_library",
"//scion-pki:go_default_library",
"//scion-pki/encoding:go_default_library",
Expand Down
4 changes: 2 additions & 2 deletions scion-pki/key/fingerprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/scionproto/scion/pkg/private/serrors"
"github.com/scionproto/scion/pkg/scrypto/cppki"
"github.com/scionproto/scion/private/app"
"github.com/scionproto/scion/private/app/command"
"github.com/scionproto/scion/scion-pki/encoding"
)
Expand Down Expand Up @@ -102,7 +102,7 @@ The subject key ID is written to standard out.

// loadPublicKey loads the public key from file and distinguishes what type of key it is.
func loadPublicKey(filename string) (crypto.PublicKey, error) {
raw, err := os.ReadFile(filename)
raw, err := app.ReadFileOrStdin(filename)
if err != nil {
return nil, serrors.Wrap("reading input file", err)
}
Expand Down
4 changes: 2 additions & 2 deletions scion-pki/key/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"

"github.com/scionproto/scion/pkg/private/serrors"
"github.com/scionproto/scion/private/app"
"github.com/scionproto/scion/private/app/command"
scionpki "github.com/scionproto/scion/scion-pki"
"github.com/scionproto/scion/scion-pki/file"
Expand Down Expand Up @@ -98,7 +98,7 @@ By default, the public key is written to standard out.
// LoadPrivate key loads a private key from file.
func LoadPrivateKey(kms, name string) (crypto.Signer, error) {
if kms == "" {
raw, err := os.ReadFile(name)
raw, err := app.ReadFileOrStdin(name)
if err != nil {
return nil, serrors.Wrap("reading private key", err)
}
Expand Down
1 change: 1 addition & 0 deletions scion-pki/trcs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ go_library(
"//pkg/scrypto/cms/oid:go_default_library",
"//pkg/scrypto/cms/protocol:go_default_library",
"//pkg/scrypto/cppki:go_default_library",
"//private/app:go_default_library",
"//private/app/command:go_default_library",
"//scion-pki:go_default_library",
"//scion-pki/conf:go_default_library",
Expand Down
5 changes: 3 additions & 2 deletions scion-pki/trcs/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ package trcs

import (
"encoding/pem"
"os"

"github.com/scionproto/scion/pkg/scrypto/cppki"
"github.com/scionproto/scion/private/app"
)

// DecodeFromFile decodes a signed TRC from the provided file.
// In case the name is "-", we read from stdin.
func DecodeFromFile(name string) (cppki.SignedTRC, error) {
raw, err := os.ReadFile(name)
raw, err := app.ReadFileOrStdin(name)
if err != nil {
return cppki.SignedTRC{}, err
}
Expand Down
117 changes: 104 additions & 13 deletions scion-pki/trcs/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ import (
"encoding/pem"
"fmt"
"os"
"slices"
"strings"

"github.com/spf13/cobra"

"github.com/scionproto/scion/pkg/addr"
"github.com/scionproto/scion/pkg/private/serrors"
"github.com/scionproto/scion/pkg/scrypto/cppki"
"github.com/scionproto/scion/private/app/command"
)

Expand Down Expand Up @@ -71,7 +75,7 @@ To inspect the created asn.1 file you can use the openssl tool::
Bytes: raw,
})
}
if err := os.WriteFile(flags.out, raw, 0644); err != nil {
if err := os.WriteFile(flags.out, raw, 0o644); err != nil {
return serrors.Wrap("failed to write extracted payload", err)
}
fmt.Printf("Successfully extracted payload at %s\n", flags.out)
Expand All @@ -86,51 +90,138 @@ To inspect the created asn.1 file you can use the openssl tool::

func newExtractCertificates(pather command.Pather) *cobra.Command {
var flags struct {
out string
out string
ias []string
types []string
}

cmd := &cobra.Command{
Use: "certificates",
Aliases: []string{"certs"},
Aliases: []string{"certs", "certificate", "cert"},
Short: "Extract the bundled certificates",
Example: fmt.Sprintf(` %[1]s certificates -o bundle.pem input.trc`, pather.CommandPath()),
Long: `'certificates' extracts the certificates into a bundled PEM file.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if err := runExtractCertificates(args[0], flags.out); err != nil {
types := make(map[cppki.CertType]bool)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: sets can be used for types and ias (like map[cppki.CertType]struct{}) to reduce the ambiguity a bit.

The checks can then just be like:

if len(ias) > 0 && !ias[ia] {
	continue
}

for _, t := range flags.types {
if t == "any" {
types = nil // No filter, all types are included.
break
}
typ, ok := certTypes[t]
if !ok {
return fmt.Errorf("unknown certificate type %q, valid types are: %s",
t, strings.Join(getTypes(), ", "))
}
types[typ] = true
}

ias := make(map[addr.IA]bool)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ias := make(map[addr.IA]bool)
ias := make(map[addr.IA]bool, len(flags.ias))

for _, v := range flags.ias {
ia, err := addr.ParseIA(v)
if err != nil {
return fmt.Errorf("invalid ISD-AS %q: %w", v, err)
}
ias[ia] = true
}

cmd.SilenceUsage = true

if err := runExtractCertificates(args[0], flags.out, types, ias); err != nil {
return err
}
fmt.Printf("Successfully extracted certificates at %s\n", flags.out)
if flags.out != "" && flags.out != "-" {
fmt.Fprintf(cmd.ErrOrStderr(),
"Successfully extracted certificates at %s\n", flags.out)
}
return nil
},
}

addOutputFlag(&flags.out, cmd)
addOptionalOutputFlag(&flags.out, cmd)

cmd.Flags().StringSliceVar(&flags.ias, "subject.isd-as", nil,
"Filter certificates by ISD-AS of the subject (e.g., 1-ff00:0:110)")
cmd.Flags().StringSliceVar(&flags.types, "type", nil,
"Filter certificates by type ("+strings.Join(getTypes(), "|")+")")
return cmd
}

func runExtractCertificates(in, out string) error {
func runExtractCertificates(
in, out string, types map[cppki.CertType]bool, ias map[addr.IA]bool,
) error {
signed, err := DecodeFromFile(in)
if err != nil {
return serrors.Wrap("failed to load signed TRC", err)
}
return writeBundle(out, signed.TRC.Certificates)
certs := make([]*x509.Certificate, 0, len(signed.TRC.Certificates))

// Filter the certificates based on the user input.
for _, cert := range signed.TRC.Certificates {
// Check certificate type
{
typ, err := cppki.ValidateCert(cert)
if err != nil {
return fmt.Errorf("invalid certificate %s: %w", cert.Subject.CommonName, err)
}
if len(types) > 0 && !types[typ] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if types contains typ then len(types) cannot be 0, right? Hence len(types) > 0 looks pointless to me, am I missing something?

continue
}
}

// Check certificate ISD-AS
{
ia, err := cppki.ExtractIA(cert.Subject)
if err != nil {
return fmt.Errorf("failed to extract ISD-AS from certificate %s: %w",
cert.Subject.CommonName, err)
}
if len(ias) > 0 && !ias[ia] {
continue
}
}
certs = append(certs, cert)
}

return writeBundle(out, certs)
}

func writeBundle(out string, certs []*x509.Certificate) error {
file, err := os.Create(out)
if err != nil {
return serrors.Wrap("unable to create file", err)
o := os.Stdout
if out != "" && out != "-" {
var err error
if o, err = os.Create(out); err != nil {
return serrors.Wrap("unable to create file", err)
}
defer o.Close()
}
defer file.Close()
for i, cert := range certs {
block := pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
}
if err := pem.Encode(file, &block); err != nil {
if err := pem.Encode(o, &block); err != nil {
return serrors.Wrap("unable to encode certificate", err, "index", i)
}
}
return nil
}

var certTypes = map[string]cppki.CertType{
cppki.Root.String(): cppki.Root,
cppki.CA.String(): cppki.CA,
cppki.AS.String(): cppki.AS,
cppki.Sensitive.String(): cppki.Sensitive,
cppki.Regular.String(): cppki.Regular,
}

func getTypes() []string {
options := make([]string, 0, len(certTypes)+1)
for k := range certTypes {
options = append(options, k)
}
options = append(options, "any")
slices.Sort(options)
return options
}
Loading
Loading