...

Text file src/github.com/sigstore/rekor/pkg/pki/ssh/README.md

Documentation: github.com/sigstore/rekor/pkg/pki/ssh

     1# SSH File Signatures
     2
     3SSH keys can be used to sign files!
     4Unfortunately this is a pretty recent change to the openssh tooling, so it is not
     5supported by golang.org/x/crypto/ssh yet.
     6
     7This document explains how it works at a high level.
     8
     9## Keys
    10
    11SSH keys are usually split into public and private files, named `id_rsa.pub` and
    12`id_rsa`, respectively.
    13These files are encoded and formatted a little differently than other signing keys.
    14
    15### Public Keys
    16
    17These are typically in the "known hosts" format.
    18This looks something like:
    19
    20```
    21ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDw0ZWP4zZLELSJVenQTQsrFJVBnoP64KTg/UWRU6qOb8HEOdtHJDOyTmo9dvN/yJoTFtWAfQEjaTsMVJzTD0gOk6ncTsp0BUtgXawSCfEUiv7v+2VgSVbUfAv/NL+HEGSCdcORnansIyrZaHwAjR3ei3O+pRWvgjRj3pOH1rWGrxaC5IbsELYzS/HvwAG/uwcxgBv4POvaq6eCEHVbqRjIYjjoYsC+c24sgSQxOyXvDS7j2z9TPHPvepDhVr9y6xnnqhLqZEWmidRrbb35aYkVLJxmGTFy/JW1cewyU2Jb3+sKQOiOwL7DAB39tRyec2ed+EHh6QLW4pcMnoXsWuPyi+G595HiUYmIlqXJ5JPo0Cv/rOJrmWSFceWiDjC/SeODp/AcK0EsN/p3wOp6ac7EzAz9Npri0vwSQX4MUYlya/olKiKCx5GIhTZtXioREPd8v4osx2VrVyDxKX99PVVbxw1FXSe4u+PuOawJzUA4vW41mxUY9zoAsb/fvoNPtrrT9HfC+7Pg6ryBdz+445M8Atc8YjjLeYXkTXWD6KMielRzBFFoIwIgi0bMotq3iQ9IwjQSXPMDQLb+UPg8xqsgRsX3wvyZzdBhxO4Bdomv7JYmySysaGgliHktU8qRse1lpDIXMovPtowywcKL4U3seDKrq7saVO0qdsLavy1o0w== lorenc.d@gmail.com
    22```
    23
    24These can be parsed with [ParseKnownHosts](https://pkg.go.dev/golang.org/x/crypto/ssh#ParseKnownHosts)
    25, NOT `ParsePublicKey`.
    26
    27In addition to the key material itself, this can contain the algorithm (`ssh-rsa` here) and a comment
    28(lorenc.d@gmail.com) here.
    29
    30### Private Keys
    31
    32These are stored in an "armored" PEM format, resembling PGP or x509 keys:
    33
    34```
    35-----BEGIN SSH PRIVATE KEY-----
    36<base64 encoded key here>
    37-----END SSH PRIVATE KEY-----
    38```
    39
    40These can be parsed correctly with [ParsePrivateKey](https://pkg.go.dev/golang.org/x/crypto/ssh#ParsePrivateKey).
    41
    42## Wire Format
    43
    44The wire format is relatively standard.
    45
    46* Bytes are laid out in order.
    47* Fixed-length fields are laid out at the proper offset with the specified length.
    48* Strings are stored with the size as a prefix.
    49
    50## Signature
    51
    52These can be generated and validated from the command line with the `ssh-keygen -Y` set of commands:
    53`sign`, `verify`, and `check-novalidate`.
    54
    55To work with them in Go is a little tricker.
    56The signature is stored using a struct packed using the `openssh` wire format.
    57The data that is used in the signing function is also packed in another struct before it is signed.
    58
    59### Signature Format
    60
    61Signatures are formatted on disk in a PEM-encoded format.
    62The header is `-----BEGIN SSH SIGNATURE-----`, and the end is `-----END SSH SIGNATURE-----`.
    63The signature contents are base64-encoded.
    64
    65The signature contents are wrapped with extra metadata, then encoded as a struct using the
    66`openssh` wire format.
    67That struct is defined [here](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L34).
    68
    69In Go:
    70
    71```
    72type WrappedSig struct {
    73	MagicHeader   [6]byte
    74	Version       uint32
    75	PublicKey     string
    76	Namespace     string
    77	Reserved      string
    78	HashAlgorithm string
    79	Signature     string
    80}
    81```
    82
    83The `PublicKey` and `Signature` fields are also stored as openssh-wire-formatted structs.
    84The `MagicHeader` is `SSHSIG`.
    85The `Version` is 1.
    86The `Namespace` is `file` (for this use-case).
    87`Reserved` must be empty.
    88
    89Go can already parse the `PublicKey` and `Signature` fields,
    90and the `Signature` struct contains a `Blob` with the signature data.
    91
    92### Signed Message
    93
    94In addition to these wrappers, the message to be signed is wrapped with some metadata before
    95it is passed to the signing function.
    96
    97That wrapper is defined [here](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L81).
    98
    99And in Go:
   100
   101```
   102type MessageWrapper struct {
   103	Namespace     string
   104	Reserved      string
   105	HashAlgorithm string
   106	Hash          string
   107}
   108```.
   109
   110So, the data must first be hashed, then packed in this struct and encoded in the
   111openssh wire format.
   112Then, this resulting data is signed using the desired signature function.
   113
   114The `Namespace` field must be `file` (for this usecase).
   115The `Reserved` field must be empty.
   116
   117The output of this signature function (and the hash) becomes the `Signature.Blob`
   118value, which gets wire-encoded, wrapped, wire-encoded and finally pem-encoded.

View as plain text