SSH public key: Go parsing library

SSH public keys are encoded in a special binary format, typically represented to end users as either one-liner OpenSSH format or multi-line PEM format (commerical SSH). Text wrapper carries extra information about user who created the key, comment, etc, but the inner binary is always base64-encoded and follows the same internal format.

This format spec deals with this internal binary format (called "blob" in openssh sources) only. Buffer is expected to be raw binary and not base64-d. Implementation closely follows code in OpenSSH.

KS implementation details

License: CC0-1.0

This page hosts a formal specification of SSH public key using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Go source code to parse SSH public key

ssh_public_key.go

// Code generated by kaitai-struct-compiler from a .ksy source file. DO NOT EDIT.

import "github.com/kaitai-io/kaitai_struct_go_runtime/kaitai"


/**
 * SSH public keys are encoded in a special binary format, typically represented
 * to end users as either one-liner OpenSSH format or multi-line PEM format
 * (commerical SSH). Text wrapper carries extra information about user who
 * created the key, comment, etc, but the inner binary is always base64-encoded
 * and follows the same internal format.
 * 
 * This format spec deals with this internal binary format (called "blob" in
 * openssh sources) only. Buffer is expected to be raw binary and not base64-d.
 * Implementation closely follows code in OpenSSH.
 * @see <a href="https://github.com/openssh/openssh-portable/blob/b4d4eda6/sshkey.c#L1970">Source</a>
 */
type SshPublicKey struct {
	KeyName *SshPublicKey_Cstring
	Body kaitai.Struct
	_io *kaitai.Stream
	_root *SshPublicKey
	_parent kaitai.Struct
}
func NewSshPublicKey() *SshPublicKey {
	return &SshPublicKey{
	}
}

func (this SshPublicKey) IO_() *kaitai.Stream {
	return this._io
}

func (this *SshPublicKey) Read(io *kaitai.Stream, parent kaitai.Struct, root *SshPublicKey) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp1 := NewSshPublicKey_Cstring()
	err = tmp1.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.KeyName = tmp1
	switch (this.KeyName.Value) {
	case "ecdsa-sha2-nistp256":
		tmp2 := NewSshPublicKey_KeyEcdsa()
		err = tmp2.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp2
	case "ssh-dss":
		tmp3 := NewSshPublicKey_KeyDsa()
		err = tmp3.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp3
	case "ssh-ed25519":
		tmp4 := NewSshPublicKey_KeyEd25519()
		err = tmp4.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp4
	case "ssh-rsa":
		tmp5 := NewSshPublicKey_KeyRsa()
		err = tmp5.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp5
	}
	return err
}

/**
 * Big integers serialization format used by ssh, v2. In the code, the following
 * routines are used to read/write it:
 * 
 * * sshbuf_get_bignum2
 * * sshbuf_get_bignum2_bytes_direct
 * * sshbuf_put_bignum2
 * * sshbuf_get_bignum2_bytes_direct
 * @see <a href="https://github.com/openssh/openssh-portable/blob/b4d4eda6/sshbuf-getput-crypto.c#L35
 * https://github.com/openssh/openssh-portable/blob/b4d4eda6/sshbuf-getput-basic.c#L431
 * ">Source</a>
 */
type SshPublicKey_Bignum2 struct {
	Len uint32
	Body []byte
	_io *kaitai.Stream
	_root *SshPublicKey
	_parent kaitai.Struct
	_f_lengthInBits bool
	lengthInBits int
}
func NewSshPublicKey_Bignum2() *SshPublicKey_Bignum2 {
	return &SshPublicKey_Bignum2{
	}
}

func (this SshPublicKey_Bignum2) IO_() *kaitai.Stream {
	return this._io
}

func (this *SshPublicKey_Bignum2) Read(io *kaitai.Stream, parent kaitai.Struct, root *SshPublicKey) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp6, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.Len = uint32(tmp6)
	tmp7, err := this._io.ReadBytes(int(this.Len))
	if err != nil {
		return err
	}
	tmp7 = tmp7
	this.Body = tmp7
	return err
}

/**
 * Length of big integer in bits. In OpenSSH sources, this corresponds to
 * `BN_num_bits` function.
 */
func (this *SshPublicKey_Bignum2) LengthInBits() (v int, err error) {
	if (this._f_lengthInBits) {
		return this.lengthInBits, nil
	}
	this._f_lengthInBits = true
	this.lengthInBits = int((this.Len - 1) * 8)
	return this.lengthInBits, nil
}

/**
 * A integer-prefixed string designed to be read using `sshbuf_get_cstring`
 * and written by `sshbuf_put_cstring` routines in ssh sources. Name is an
 * obscure misnomer, as typically "C string" means a null-terminated string.
 * @see <a href="https://github.com/openssh/openssh-portable/blob/b4d4eda6/sshbuf-getput-basic.c#L181">Source</a>
 */
type SshPublicKey_Cstring struct {
	Len uint32
	Value string
	_io *kaitai.Stream
	_root *SshPublicKey
	_parent kaitai.Struct
}
func NewSshPublicKey_Cstring() *SshPublicKey_Cstring {
	return &SshPublicKey_Cstring{
	}
}

func (this SshPublicKey_Cstring) IO_() *kaitai.Stream {
	return this._io
}

func (this *SshPublicKey_Cstring) Read(io *kaitai.Stream, parent kaitai.Struct, root *SshPublicKey) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp8, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.Len = uint32(tmp8)
	tmp9, err := this._io.ReadBytes(int(this.Len))
	if err != nil {
		return err
	}
	tmp9 = tmp9
	this.Value = string(tmp9)
	return err
}

/**
 * Elliptic curve dump format used by ssh. In OpenSSH code, the following
 * routines are used to read/write it:
 * 
 * * sshbuf_get_ec
 * * get_ec
 * @see <a href="https://github.com/openssh/openssh-portable/blob/b4d4eda6/sshbuf-getput-crypto.c#L90
 * https://github.com/openssh/openssh-portable/blob/b4d4eda6/sshbuf-getput-crypto.c#L76
 * ">Source</a>
 */
type SshPublicKey_EllipticCurve struct {
	Len uint32
	Body []byte
	_io *kaitai.Stream
	_root *SshPublicKey
	_parent *SshPublicKey_KeyEcdsa
}
func NewSshPublicKey_EllipticCurve() *SshPublicKey_EllipticCurve {
	return &SshPublicKey_EllipticCurve{
	}
}

func (this SshPublicKey_EllipticCurve) IO_() *kaitai.Stream {
	return this._io
}

func (this *SshPublicKey_EllipticCurve) Read(io *kaitai.Stream, parent *SshPublicKey_KeyEcdsa, root *SshPublicKey) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp10, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.Len = uint32(tmp10)
	tmp11, err := this._io.ReadBytes(int(this.Len))
	if err != nil {
		return err
	}
	tmp11 = tmp11
	this.Body = tmp11
	return err
}

/**
 * @see <a href="https://github.com/openssh/openssh-portable/blob/b4d4eda6/sshkey.c#L2036-L2051">Source</a>
 */
type SshPublicKey_KeyDsa struct {
	DsaP *SshPublicKey_Bignum2
	DsaQ *SshPublicKey_Bignum2
	DsaG *SshPublicKey_Bignum2
	DsaPubKey *SshPublicKey_Bignum2
	_io *kaitai.Stream
	_root *SshPublicKey
	_parent *SshPublicKey
}
func NewSshPublicKey_KeyDsa() *SshPublicKey_KeyDsa {
	return &SshPublicKey_KeyDsa{
	}
}

func (this SshPublicKey_KeyDsa) IO_() *kaitai.Stream {
	return this._io
}

func (this *SshPublicKey_KeyDsa) Read(io *kaitai.Stream, parent *SshPublicKey, root *SshPublicKey) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp12 := NewSshPublicKey_Bignum2()
	err = tmp12.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.DsaP = tmp12
	tmp13 := NewSshPublicKey_Bignum2()
	err = tmp13.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.DsaQ = tmp13
	tmp14 := NewSshPublicKey_Bignum2()
	err = tmp14.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.DsaG = tmp14
	tmp15 := NewSshPublicKey_Bignum2()
	err = tmp15.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.DsaPubKey = tmp15
	return err
}

/**
 * @see <a href="https://github.com/openssh/openssh-portable/blob/b4d4eda6/sshkey.c#L2060-L2103">Source</a>
 */
type SshPublicKey_KeyEcdsa struct {
	CurveName *SshPublicKey_Cstring
	Ec *SshPublicKey_EllipticCurve
	_io *kaitai.Stream
	_root *SshPublicKey
	_parent *SshPublicKey
}
func NewSshPublicKey_KeyEcdsa() *SshPublicKey_KeyEcdsa {
	return &SshPublicKey_KeyEcdsa{
	}
}

func (this SshPublicKey_KeyEcdsa) IO_() *kaitai.Stream {
	return this._io
}

func (this *SshPublicKey_KeyEcdsa) Read(io *kaitai.Stream, parent *SshPublicKey, root *SshPublicKey) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp16 := NewSshPublicKey_Cstring()
	err = tmp16.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.CurveName = tmp16
	tmp17 := NewSshPublicKey_EllipticCurve()
	err = tmp17.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.Ec = tmp17
	return err
}

/**
 * @see <a href="https://github.com/openssh/openssh-portable/blob/b4d4eda6/sshkey.c#L2111-L2124">Source</a>
 */
type SshPublicKey_KeyEd25519 struct {
	LenPk uint32
	Pk []byte
	_io *kaitai.Stream
	_root *SshPublicKey
	_parent *SshPublicKey
}
func NewSshPublicKey_KeyEd25519() *SshPublicKey_KeyEd25519 {
	return &SshPublicKey_KeyEd25519{
	}
}

func (this SshPublicKey_KeyEd25519) IO_() *kaitai.Stream {
	return this._io
}

func (this *SshPublicKey_KeyEd25519) Read(io *kaitai.Stream, parent *SshPublicKey, root *SshPublicKey) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp18, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.LenPk = uint32(tmp18)
	tmp19, err := this._io.ReadBytes(int(this.LenPk))
	if err != nil {
		return err
	}
	tmp19 = tmp19
	this.Pk = tmp19
	return err
}

/**
 * @see <a href="https://github.com/openssh/openssh-portable/blob/b4d4eda6/sshkey.c#L2011-L2028">Source</a>
 */
type SshPublicKey_KeyRsa struct {
	RsaE *SshPublicKey_Bignum2
	RsaN *SshPublicKey_Bignum2
	_io *kaitai.Stream
	_root *SshPublicKey
	_parent *SshPublicKey
	_f_keyLength bool
	keyLength int
}
func NewSshPublicKey_KeyRsa() *SshPublicKey_KeyRsa {
	return &SshPublicKey_KeyRsa{
	}
}

func (this SshPublicKey_KeyRsa) IO_() *kaitai.Stream {
	return this._io
}

func (this *SshPublicKey_KeyRsa) Read(io *kaitai.Stream, parent *SshPublicKey, root *SshPublicKey) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp20 := NewSshPublicKey_Bignum2()
	err = tmp20.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.RsaE = tmp20
	tmp21 := NewSshPublicKey_Bignum2()
	err = tmp21.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.RsaN = tmp21
	return err
}

/**
 * Key length in bits
 */
func (this *SshPublicKey_KeyRsa) KeyLength() (v int, err error) {
	if (this._f_keyLength) {
		return this.keyLength, nil
	}
	this._f_keyLength = true
	tmp22, err := this.RsaN.LengthInBits()
	if err != nil {
		return 0, err
	}
	this.keyLength = int(tmp22)
	return this.keyLength, nil
}

/**
 * Public key exponent, designated `e` in RSA documentation.
 */

/**
 * Modulus of both public and private keys, designated `n` in RSA
 * documentation. Its length in bits is designated as "key length".
 */