sshsig

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Mar 17, 2023 License: Apache-2.0 Imports: 8 Imported by: 3

README

sshsig

Go Reference Go Report Card

This Go library implements the SSHSIG wire protocol, and can be used to sign and verify messages using SSH keys.

Compared to other implementations, this library does all the following:

  • Accepts an io.Reader as input for signing and verifying messages.
  • Performs simple public key fingerprint and namespace mismatch checks in Verify. Malicious input will still fail signature verification, but this provides more useful error messages.
  • Properly uses ssh-sha2-512 as signature algorithm when signing with an RSA private key, as described in the protocol.
  • Does not accept a Sign operation without a namespace as specified in the protocol.
  • Allows Verify operations to be performed without a namespace, ensuring compatibility with loose implementations.
  • Provides Armor and Unarmor functions to encode/decode the signature to/from an (armored) PEM format.

For more information about the use of this library, see the Go Reference.

Acknowledgements

There are several other implementations of the SSHSIG protocol in Go, from which this library has borrowed ideas:

Documentation

Overview

Package sshsig provides an API to sign and verify messages using SSH keys. It is an implementation of the SSH Signature format as described in https://github.com/openssh/openssh-portable/blob/V_9_3_P1/PROTOCOL.sshsig.

Index

Examples

Constants

View Source
const PEMType = "SSH SIGNATURE"

PEMType is the PEM type of an armored SSH signature.

Variables

View Source
var (
	// ErrUnsupportedHashAlgorithm is returned by Sign and Verify if the hash
	// algorithm is not supported.
	ErrUnsupportedHashAlgorithm = errors.New("unsupported hash algorithm")
	// ErrUnavailableHashAlgorithm is returned by Sign and Verify if the hash
	// algorithm is not available.
	ErrUnavailableHashAlgorithm = errors.New("unavailable hash algorithm")
)
View Source
var (
	// ErrPublicKeyMismatch is returned by Verify if the public key in the signature
	// does not match the public key used to verify the signature.
	ErrPublicKeyMismatch = errors.New("public key does not match")

	// ErrNamespaceMismatch is returned by Verify if the namespace in the signature
	// does not match the namespace used to verify the signature.
	ErrNamespaceMismatch = errors.New("namespace does not match")
)
View Source
var (
	// ErrUnsupportedSignatureVersion is returned when the signature version is
	// not supported.
	ErrUnsupportedSignatureVersion = errors.New("unsupported signature version")
	// ErrInvalidMagicPreamble is returned when the magic preamble is invalid.
	ErrInvalidMagicPreamble = errors.New("invalid magic preamble")
)
View Source
var ErrMissingNamespace = errors.New("missing namespace")

ErrMissingNamespace is returned by Sign if the namespace value is missing.

Functions

func Armor

func Armor(s *Signature) []byte

Armor returns a PEM-encoded Signature. It does not perform any validation.

func Verify

func Verify(m io.Reader, sig *Signature, pub ssh.PublicKey, h HashAlgorithm, namespace string) error

Verify verifies the message from the io.Reader matches the Signature using the given ssh.PublicKey and HashAlgorithm.

The purpose of the namespace value is to specify an unambiguous interpretation domain for the signature, e.g. file signing. This prevents cross-protocol attacks caused by signatures intended for one intended domain being accepted in another. Unlike Sign, the namespace value is not required to allow verification of signatures created by looser implementations.

Verify returns an error if the verification process fails.

Example
// Load a public key to verify with.
pub, _, _, _, err := ssh.ParseAuthorizedKey([]byte(ed25519PublicKey))
if err != nil {
	panic(err)
}

// Load the armored (PEM) signature to verify.
armored := []byte(`-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1
NgAAAEEEOIYuWF0v/w8XVrOLUa30nMhLwiXdsf4aow88kfpnfA/Zn+Xhr9nRh97e
tNV1/Kqv1VE/On/YH+094IhlatyELQAAAARmaWxlAAAAAAAAAAZzaGE1MTIAAABk
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAABJAAAAIBXp90537Om8Xbv0iTxVwvSy
iZmhAca7kPt0uSg0IVtTAAAAIQCbN+co4miAJ7t9XLIQuOaOQCM5P0AxRCdsMG4e
BnAL0w==
-----END SSH SIGNATURE-----`)
sig, err := sshsig.Unarmor(armored)
if err != nil {
	panic(err)
}

// Verify the signature, using the same hash algorithm and namespace as
// used to sign the message. If the signature is valid, no error is
// returned.
message := []byte("Hello world!")
if err := sshsig.Verify(bytes.NewReader(message), sig, pub, sig.HashAlgorithm, sig.Namespace); err != nil {
	panic(err)
}

// When more strict verification is required, the hash algorithm and/or
// namespace can be checked against the expected values.
if err := sshsig.Verify(bytes.NewReader(message), sig, pub, sshsig.HashSHA512, "file"); err != nil {
	panic(err)
}
Output:

Types

type HashAlgorithm

type HashAlgorithm string

HashAlgorithm represents an algorithm used to compute a hash of a message.

const (
	// HashSHA256 is the SHA-256 hash algorithm.
	HashSHA256 HashAlgorithm = "sha256"
	// HashSHA512 is the SHA-512 hash algorithm.
	HashSHA512 HashAlgorithm = "sha512"
)

func SupportedHashAlgorithms

func SupportedHashAlgorithms() []HashAlgorithm

SupportedHashAlgorithms returns a list of supported hash algorithms.

func (HashAlgorithm) Available

func (h HashAlgorithm) Available() error

Available returns ErrUnsupportedHashAlgorithm if the hash algorithm is not supported, ErrUnavailableHashAlgorithm if the hash algorithm is not available, nil otherwise.

func (HashAlgorithm) Hash

func (h HashAlgorithm) Hash() hash.Hash

Hash returns a hash.Hash for the hash algorithm. If the hash algorithm is not available, it panics. The library itself ensures that the hash algorithm is available before calling this function.

func (HashAlgorithm) String

func (h HashAlgorithm) String() string

String returns the string representation of the hash algorithm.

func (HashAlgorithm) Supported

func (h HashAlgorithm) Supported() error

Supported returns ErrUnsupportedHashAlgorithm if the hash algorithm is not supported, nil otherwise. Use Available if the intention is to make use of the hash algorithm, as it also checks if the hash algorithm is available.

type Signature

type Signature struct {
	// Version is the version of the signature format.
	// It currently supports version 1, any other value will be rejected with
	// ErrUnsupportedSignatureVersion.
	Version uint32
	// PublicKey is the public key used to create the Signature.
	PublicKey ssh.PublicKey
	// Namespace is the domain of the signature, and is used to prevent signature
	// reuse across different applications.
	Namespace string
	// HashAlgorithm is the hash algorithm used to hash the Signature message.
	HashAlgorithm HashAlgorithm
	// Signature is the SSH signature of the hash of the message.
	Signature *ssh.Signature
}

Signature represents the SSH signature of a message. It can be marshaled into an SSH wire format using Marshal, or into an armored (PEM) format using Armor.

Manually construction of this type is not recommended. Use ParseSignature or Unarmor instead to retrieve a Signature from a wire or armored (PEM) format.

func ParseSignature

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

ParseSignature parses a signature in SSH wire format into a Signature. It returns an error if the signature is invalid.

func Sign

func Sign(m io.Reader, signer ssh.Signer, h HashAlgorithm, namespace string) (*Signature, error)

Sign generates a signature of the message from the io.Reader using the given ssh.Signer private key. The signature hash is computed using the provided HashAlgorithm.

The purpose of the namespace value is to specify an unambiguous interpretation domain for the signature, e.g. file signing. This prevents cross-protocol attacks caused by signatures intended for one intended domain being accepted in another. The namespace must not be empty, or ErrMissingNamespace will be returned.

When the signer is an RSA key, the signature algorithm will always be "rsa-sha2-512". This is the same default used by OpenSSH, and is required by the SSH signature wire protocol.

Sign returns a Signature containing the signed message and metadata, or an error if the signing process fails.

Example
// Load the private key to sign with.
signer, err := ssh.ParsePrivateKey([]byte(ecdsaPrivateKey))
if err != nil {
	panic(err)
}

// Sign a message with the private key, using an SHA-512 hash and the
// namespace "file".
message := []byte("Hello world!")
sig, err := sshsig.Sign(bytes.NewReader(message), signer, sshsig.HashSHA512, "file")
if err != nil {
	panic(err)
}

// Print the signature in armored (PEM) format.
armored := sshsig.Armor(sig)
fmt.Printf("%s", armored)
Output:

func SignWithRand

func SignWithRand(m, rand io.Reader, signer ssh.Signer, h HashAlgorithm, namespace string) (*Signature, error)

SignWithRand is like Sign, but uses the provided rand io.Reader to create any necessary random values. Most callers likely want to use Sign instead.

func Unarmor

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

Unarmor decodes a PEM-encoded signature into a Signature. It returns an error if the PEM block is invalid or the signature parsing fails.

func (Signature) Marshal

func (s Signature) Marshal() []byte

Marshal returns the Signature in SSH wire format.

Jump to

Keyboard shortcuts

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