Class: SSHPublicKey

Inherits:
Object
  • Object
show all
Defined in:
lib/ssh-publickey.rb,
lib/ssh-publickey/version.rb

Overview

Convert SSH public key from/to RFC4716/OpenSSH format

Defined Under Namespace

Classes: PublicKeyError

Constant Summary collapse

LINE_BREAK =

Maximum text size in a line for PEM format

70
AUTHORIZED_KEY_REGEX =

Regex for authorized key

/^(?:    (?<directives>(?:[!-~]|\s(?=.*"))+) \s+ )?   # Options
        (?<type>      [a-z0-9_-]+)          \s+      # Type
        (?<key>       [A-Za-z0-9+\/]+=*)             # Key
 (?:\s+ (?<comment>.*?)                     \s* )?   # Comments
$/x
PUBKEY_OPENSSH_REGEX =

Regex for OpenSSH public key Same as authorized keys but without directives

/^       (?<type>   [a-z0-9_-]+)             \s+      # Type
        (?<key>    [A-Za-z0-9+\/]+=*)                # Key
 (?:\s+ (?<comment>.*?)                     \s* )?   # Comments
$/x
PUBKEY_RFC4716_REGEX =

Regex for RFC4716 public key

/^----\sBEGIN\sSSH2\sPUBLIC\sKEY\s----\R
 (?<tags> (?:[^:\p{Space}\p{Cntrl}]{1,64}            # Tags
             \s*:\s*
             (?:[^\\\r\n]*\\\R)* [^\\\r\n]+\R)*)
 (?<key>  (?:[A-Za-z0-9+\/]+\R)*                     # Key
          [A-Za-z0-9+\/]+=*\R)
 ----\sEND\sSSH2\sPUBLIC\sKEY\s----
$/xmu
PUBKEY_RFC4716_HEADERTAG_REGEX =

:nodoc:

/[^:\p{Space}\p{Cntrl}]{1,64}
\s*:\s*   
(?:[^\\\r\n]*\\\R)*[^\\\r\n]+\R/xmu
VERSION =

Current version

'0.1'

Class Method Summary collapse

Class Method Details

.is_openssh?(pubkey) ⇒ Boolean

Test if a public key is in OpenSSH format

Parameters:

  • pubkey (String)

    public key in RFC4716 format

Returns:

  • (Boolean)


117
118
119
# File 'lib/ssh-publickey.rb', line 117

def self.is_openssh?(pubkey)
    PUBKEY_RFC4716_REGEX.match?(pubkey)
end

.is_rfc4716?(pubkey) ⇒ Boolean

Test if a public key is in RFC4716 format

Parameters:

  • pubkey (String)

    public key in RFC4716 format

Returns:

  • (Boolean)


128
129
130
# File 'lib/ssh-publickey.rb', line 128

def self.is_rfc4716?(pubkey)
    PUBKEY_RFC4716_REGEX.match?(pubkey)
end

.openssh_to_rfc4716(pubkey) ⇒ String

Convert a public key in OpenSSH format to RFC4716 format

Parameters:

  • pubkey (String)

    public key in OpenSSH format

Returns:

  • (String)

    public key in RFC4716 format



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/ssh-publickey.rb', line 58

def self.openssh_to_rfc4716(pubkey)
    unless m = AUTHORIZED_KEY_REGEX.match(pubkey)
        raise PublicKeyError, "invalid OpenSSH public key"
    end

    # Lines are limited to 72 8-bytes char
    #  - limit ourselves to 70 to keep room for \\ and \n
    #  - comment part can be unicode so 1 char can be more that 1 byte
    linesize = 0
    comment  = "Comment: #{m[:comment]}"
                  .each_char.slice_before {|c|
                       bytesize = c.bytes.size 
                       if linesize + bytesize > LINE_BREAK
                       then linesize  = 0        ; true
                       else linesize += bytesize ; false
                       end
                  }.map(&:join).join("\\\n")
    key      = m[:key].scan(/.{1,70}/).join("\n")
    
    reskey   = []
    reskey   << "---- BEGIN SSH2 PUBLIC KEY ----"
    reskey   << comment
    reskey   << key
    reskey   << "---- END SSH2 PUBLIC KEY ----"
    reskey.join("\n")
end

.rfc4716_to_openssh(pubkey) ⇒ String

Convert a public key in RFC4716 format to OpenSSH format

Parameters:

  • pubkey (String)

    public key in RFC4716 format

Returns:

  • (String)

    public key in OpenSSH format



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/ssh-publickey.rb', line 92

def self.rfc4716_to_openssh(pubkey)
    unless m = PUBKEY_RFC4716_REGEX.match(pubkey) 
        raise PublicKeyError, "invalid RFC4716 public key"
    end

    key        = m[:key].gsub(/\R/, '')
    keydata    = m[:key].unpack1('m')
    len        = keydata.unpack1('N')
    type       = keydata[4,len]
    tags       = Hash[m[:tags].scan(PUBKEY_RFC4716_HEADERTAG_REGEX)
                              .map {|tag| tag.gsub(/\\\R/, '').strip }
                              .map {|tag| tag.split(/\s*:\s*/, 2)    }]
    comment    = tags.transform_keys {|k| k.downcase }['comment']
    directives = nil
    
    [directives, type,  key, comment ].compact.join(' ')
end