ssh

package
v0.8.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 21, 2022 License: Apache-2.0 Imports: 11 Imported by: 1

README

SSH File Signatures

SSH keys can be used to sign files! Unfortunately this is a pretty recent change to the openssh tooling, so it is not supported by golang.org/x/crypto/ssh yet.

This document explains how it works at a high level.

Keys

SSH keys are usually split into public and private files, named id_rsa.pub and id_rsa, respectively. These files are encoded and formatted a little differently than other signing keys.

Public Keys

These are typically in the "known hosts" format. This looks something like:

ssh-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

These can be parsed with ParseKnownHosts , NOT ParsePublicKey.

In addition to the key material itself, this can contain the algorithm (ssh-rsa here) and a comment (lorenc.d@gmail.com) here.

Private Keys

These are stored in an "armored" PEM format, resembling PGP or x509 keys:

-----BEGIN SSH PRIVATE KEY-----
<base64 encoded key here>
-----END SSH PRIVATE KEY-----

These can be parsed correctly with ParsePrivateKey.

Wire Format

The wire format is relatively standard.

  • Bytes are laid out in order.
  • Fixed-length fields are laid out at the proper offset with the specified length.
  • Strings are stored with the size as a prefix.

Signature

These can be generated and validated from the command line with the ssh-keygen -Y set of commands: sign, verify, and check-novalidate.

To work with them in Go is a little tricker. The signature is stored using a struct packed using the openssh wire format. The data that is used in the signing function is also packed in another struct before it is signed.

Signature Format

Signatures are formatted on disk in a PEM-encoded format. The header is -----BEGIN SSH SIGNATURE-----, and the end is -----END SSH SIGNATURE-----. The signature contents are base64-encoded.

The signature contents are wrapped with extra metadata, then encoded as a struct using the openssh wire format. That struct is defined here.

In Go:

type WrappedSig struct {
	MagicHeader   [6]byte
	Version       uint32
	PublicKey     string
	Namespace     string
	Reserved      string
	HashAlgorithm string
	Signature     string
}

The PublicKey and Signature fields are also stored as openssh-wire-formatted structs. The MagicHeader is SSHSIG. The Version is 1. The Namespace is file (for this use-case). Reserved must be empty.

Go can already parse the PublicKey and Signature fields, and the Signature struct contains a Blob with the signature data.

Signed Message

In addition to these wrappers, the message to be signed is wrapped with some metadata before it is passed to the signing function.

That wrapper is defined here.

And in Go:

type MessageWrapper struct {
	Namespace     string
	Reserved      string
	HashAlgorithm string
	Hash          string
}
```.

So, the data must first be hashed, then packed in this struct and encoded in the
openssh wire format.
Then, this resulting data is signed using the desired signature function.

The `Namespace` field must be `file` (for this usecase).
The `Reserved` field must be empty.

The output of this signature function (and the hash) becomes the `Signature.Blob`
value, which gets wire-encoded, wrapped, wire-encoded and finally pem-encoded.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Armor

func Armor(s *ssh.Signature, p ssh.PublicKey) []byte

func Sign

func Sign(sshPrivateKey string, data io.Reader) ([]byte, error)

func Verify

func Verify(message io.Reader, armoredSignature []byte, publicKey []byte) error

Types

type MessageWrapper

type MessageWrapper struct {
	Namespace     string
	Reserved      string
	HashAlgorithm string
	Hash          string
}

https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L81

type PublicKey

type PublicKey struct {
	// contains filtered or unexported fields
}

PublicKey contains an ssh PublicKey

func NewPublicKey

func NewPublicKey(r io.Reader) (*PublicKey, error)

NewPublicKey implements the pki.PublicKey interface

func (PublicKey) CanonicalValue

func (k PublicKey) CanonicalValue() ([]byte, error)

CanonicalValue implements the pki.PublicKey interface

func (PublicKey) EmailAddresses added in v0.2.0

func (k PublicKey) EmailAddresses() []string

EmailAddresses implements the pki.PublicKey interface

type Signature

type Signature struct {
	// contains filtered or unexported fields
}

func Decode

func Decode(b []byte) (*Signature, error)

func NewSignature

func NewSignature(r io.Reader) (*Signature, error)

NewSignature creates and Validates an ssh signature object

func (Signature) CanonicalValue

func (s Signature) CanonicalValue() ([]byte, error)

CanonicalValue implements the pki.Signature interface

func (Signature) Verify

func (s Signature) Verify(r io.Reader, k interface{}, opts ...sigsig.VerifyOption) error

Verify implements the pki.Signature interface

type WrappedSig

type WrappedSig struct {
	MagicHeader   [6]byte
	Version       uint32
	PublicKey     string
	Namespace     string
	Reserved      string
	HashAlgorithm string
	Signature     string
}

https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L34

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL