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.
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.
// 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".
*/