summaryrefslogtreecommitdiff
path: root/lib/net/ssh/authentication/certificate.rb
blob: cfd8c4eab61cb546ea79df834cb9439b712b7499 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
require 'securerandom'

module Net 
  module SSH 
    module Authentication
      # Class for representing an SSH certificate.
      #
      # http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr.bin/ssh/PROTOCOL.certkeys?rev=1.10&content-type=text/plain
      class Certificate
        attr_accessor :nonce
        attr_accessor :key
        attr_accessor :serial
        attr_accessor :type
        attr_accessor :key_id
        attr_accessor :valid_principals
        attr_accessor :valid_after
        attr_accessor :valid_before
        attr_accessor :critical_options
        attr_accessor :extensions
        attr_accessor :reserved
        attr_accessor :signature_key
        attr_accessor :signature
    
        # Read a certificate blob associated with a key of the given type.
        def self.read_certblob(buffer, type)
          cert = Certificate.new
          cert.nonce = buffer.read_string
          cert.key = buffer.read_keyblob(type)
          cert.serial = buffer.read_int64
          cert.type = type_symbol(buffer.read_long)
          cert.key_id = buffer.read_string
          cert.valid_principals = buffer.read_buffer.read_all(&:read_string)
          cert.valid_after = Time.at(buffer.read_int64)
          cert.valid_before = Time.at(buffer.read_int64)
          cert.critical_options = read_options(buffer)
          cert.extensions = read_options(buffer)
          cert.reserved = buffer.read_string
          cert.signature_key = buffer.read_buffer.read_key
          cert.signature = buffer.read_string
          cert
        end
    
        def ssh_type
          key.ssh_type + "-cert-v01@openssh.com"
        end
    
        def ssh_signature_type
          key.ssh_type
        end
    
        # Serializes the certificate (and key).
        def to_blob
          Buffer.from(
            :raw, to_blob_without_signature,
            :string, signature
          ).to_s
        end
    
        def ssh_do_sign(data)
          key.ssh_do_sign(data)
        end
    
        def ssh_do_verify(sig, data)
          key.ssh_do_verify(sig, data)
        end
    
        def to_pem
          key.to_pem
        end
    
        def fingerprint
          key.fingerprint
        end
    
        # Signs the certificate with key.
        def sign!(key, sign_nonce=nil)
          # ssh-keygen uses 32 bytes of nonce.
          self.nonce = sign_nonce || SecureRandom.random_bytes(32)
          self.signature_key = key
          self.signature = Net::SSH::Buffer.from(
            :string, key.ssh_signature_type,
            :mstring, key.ssh_do_sign(to_blob_without_signature)
          ).to_s
          self
        end
    
        def sign(key, sign_nonce=nil)
          cert = clone
          cert.sign!(key, sign_nonce)
        end
    
        # Checks whether the certificate's signature was signed by signature key.
        def signature_valid?
          buffer = Buffer.new(signature)
          buffer.read_string # skip signature format
          signature_key.ssh_do_verify(buffer.read_string, to_blob_without_signature)
        end
    
        def self.read_options(buffer)
          names = []
          options = buffer.read_buffer.read_all do |b|
            name = b.read_string
            names << name
            data = b.read_string
            data = Buffer.new(data).read_string unless data.empty?
            [name, data]
          end
    
          raise ArgumentError, "option/extension names must be in sorted order" if names.sort != names
    
          Hash[options]
        end
        private_class_method :read_options
    
        def self.type_symbol(type)
          types = { 1 => :user, 2 => :host }
          raise ArgumentError("unsupported type: #{type}") unless types.include?(type)
          types.fetch(type)
        end
        private_class_method :type_symbol
    
        private
    
        def type_value(type)
          types = { user: 1, host: 2 }
          raise ArgumentError("unsupported type: #{type}") unless types.include?(type)
          types.fetch(type)
        end
    
        def ssh_time(t)
          # Times in certificates are represented as a uint64.
          [[t.to_i, 0].max, 2 << 64 - 1].min
        end
    
        def to_blob_without_signature
          Buffer.from(
            :string, ssh_type,
            :string, nonce,
            :raw, key_without_type,
            :int64, serial,
            :long, type_value(type),
            :string, key_id,
            :string, valid_principals.inject(Buffer.new) { |acc, elem| acc.write_string(elem) }.to_s,
            :int64, ssh_time(valid_after),
            :int64, ssh_time(valid_before),
            :string, options_to_blob(critical_options),
            :string, options_to_blob(extensions),
            :string, reserved,
            :string, signature_key.to_blob
          ).to_s
        end
    
        def key_without_type
          # key.to_blob gives us e.g. "ssh-rsa,<key>" but we just want "<key>".
          tmp = Buffer.new(key.to_blob)
          tmp.read_string # skip the underlying key type
          tmp.read
        end
    
        def options_to_blob(options)
          options.keys.sort.inject(Buffer.new) do |b, name|
            b.write_string(name)
            data = options.fetch(name)
            data = Buffer.from(:string, data).to_s unless data.empty?
            b.write_string(data)
          end.to_s
        end
      end
end; end; end