Class: SSHData::Certificate

Inherits:
Object
  • Object
show all
Defined in:
lib/ssh_data/certificate.rb

Constant Summary collapse

BEGINNING_OF_TIME =

Special values for valid_before and valid_after.

Time.at(0)
END_OF_TIME =
Time.at((2**64)-1)
TYPE_USER =

Integer certificate types

1
TYPE_HOST =
2
ALGO_RSA =

Certificate algorithm identifiers

"[email protected]"
ALGO_DSA =
"[email protected]"
ALGO_ECDSA256 =
"[email protected]"
ALGO_ECDSA384 =
"[email protected]"
ALGO_ECDSA521 =
"[email protected]"
ALGO_ED25519 =
"[email protected]"
ALGO_SKECDSA256 =
"[email protected]"
ALGO_SKED25519 =
"[email protected]"
ALGOS =
[
  ALGO_RSA, ALGO_DSA, ALGO_ECDSA256, ALGO_ECDSA384, ALGO_ECDSA521,
  ALGO_ED25519, ALGO_SKECDSA256, ALGO_SKED25519
]
CRITICAL_OPTION_FORCE_COMMAND =
"force-command"
CRITICAL_OPTION_SOURCE_ADDRESS =
"source-address"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(public_key:, key_id:, algo: nil, nonce: nil, serial: 0, type: TYPE_USER, valid_principals: [], valid_after: BEGINNING_OF_TIME, valid_before: END_OF_TIME, critical_options: {}, extensions: {}, reserved: "", ca_key: nil, signature: "") ⇒ Certificate

Intialize a new Certificate instance.

algo: - The certificate’s String algorithm id (one of ALGO_RSA,

ALGO_DSA, ALGO_ECDSA256, ALGO_ECDSA384, ALGO_ECDSA521,
or ALGO_ED25519)

nonce: - The certificate’s String nonce field. public_key: - The certificate’s public key as an PublicKey::Base

subclass instance.

serial: - The certificate’s Integer serial field. type: - The certificate’s Integer type field (one of TYPE_USER

or TYPE_HOST).

key_id: - The certificate’s String key_id field. valid_principals: - The Array of Strings valid_principles field from the

certificate.

valid_after: - The certificate’s Time valid_after field. valid_before: - The certificate’s Time valid_before field. critical_options: - The Hash critical_options field from the certificate. extensions: - The Hash extensions field from the certificate. reserved: - The certificate’s String reserved field. ca_key: - The issuing CA’s public key as a PublicKey::Base

subclass instance.

signature: - The certificate’s String signature field.

Returns nothing.



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/ssh_data/certificate.rb', line 106

def initialize(public_key:, key_id:, algo: nil, nonce: nil, serial: 0, type: TYPE_USER, valid_principals: [], valid_after: BEGINNING_OF_TIME, valid_before: END_OF_TIME, critical_options: {}, extensions: {}, reserved: "", ca_key: nil, signature: "")
  @algo = algo || Encoding::CERT_ALGO_BY_PUBLIC_KEY_ALGO[public_key.algo]
  @nonce = nonce || SecureRandom.random_bytes(32)
  @public_key = public_key
  @serial = serial
  @type = type
  @key_id = key_id
  @valid_principals = valid_principals
  @valid_after = valid_after
  @valid_before = valid_before
  @critical_options = critical_options
  @extensions = extensions
  @reserved = reserved
  @ca_key = ca_key
  @signature = signature
end

Instance Attribute Details

#algoObject (readonly)

Returns the value of attribute algo.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def algo
  @algo
end

#ca_keyObject (readonly)

Returns the value of attribute ca_key.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def ca_key
  @ca_key
end

#critical_optionsObject (readonly)

Returns the value of attribute critical_options.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def critical_options
  @critical_options
end

#extensionsObject (readonly)

Returns the value of attribute extensions.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def extensions
  @extensions
end

#key_idObject (readonly)

Returns the value of attribute key_id.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def key_id
  @key_id
end

#nonceObject (readonly)

Returns the value of attribute nonce.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def nonce
  @nonce
end

#public_keyObject (readonly)

Returns the value of attribute public_key.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def public_key
  @public_key
end

#reservedObject (readonly)

Returns the value of attribute reserved.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def reserved
  @reserved
end

#serialObject (readonly)

Returns the value of attribute serial.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def serial
  @serial
end

#signatureObject (readonly)

Returns the value of attribute signature.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def signature
  @signature
end

#typeObject (readonly)

Returns the value of attribute type.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def type
  @type
end

#valid_afterObject (readonly)

Returns the value of attribute valid_after.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def valid_after
  @valid_after
end

#valid_beforeObject (readonly)

Returns the value of attribute valid_before.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def valid_before
  @valid_before
end

#valid_principalsObject (readonly)

Returns the value of attribute valid_principals.



32
33
34
# File 'lib/ssh_data/certificate.rb', line 32

def valid_principals
  @valid_principals
end

Class Method Details

.parse_openssh(cert, unsafe_no_verify: false) ⇒ Object

Parse an OpenSSH certificate in authorized_keys format (see sshd(8) manual page).

cert - An OpenSSH formatted certificate, including key algo,

base64 encoded key and optional comment.

unsafe_no_verify: - Bool of whether to skip verifying certificate signature

(Default false)

Returns a Certificate instance.



45
46
47
48
49
50
51
52
53
54
# File 'lib/ssh_data/certificate.rb', line 45

def self.parse_openssh(cert, unsafe_no_verify: false)
  algo, raw, _ = SSHData.key_parts(cert)
  parsed = parse_rfc4253(raw, unsafe_no_verify: unsafe_no_verify)

  if parsed.algo != algo
    raise DecodeError, "algo mismatch: #{parsed.algo.inspect}!=#{algo.inspect}"
  end

  parsed
end

.parse_rfc4253(raw, unsafe_no_verify: false) ⇒ Object

Parse an RFC 4253 binary SSH certificate.

cert - A RFC 4253 binary certificate String. unsafe_no_verify: - Bool of whether to skip verifying certificate

signature (Default false)

Returns a Certificate instance.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/ssh_data/certificate.rb', line 66

def self.parse_rfc4253(raw, unsafe_no_verify: false)
  data, read = Encoding.decode_certificate(raw)

  if read != raw.bytesize
    raise DecodeError, "unexpected trailing data"
  end

  # Parse data into better types, where possible.
  public_key = PublicKey.from_data(data.delete(:public_key))
  ca_key     = PublicKey.from_data(data.delete(:signature_key))

  new(**data.merge(public_key: public_key, ca_key: ca_key)).tap do |cert|
    raise VerifyError unless unsafe_no_verify || cert.verify
  end
end

Instance Method Details

#allowed_source_address?(address) ⇒ Boolean

Check if the given IP address is allowed for use with this certificate.

address - A String IP address.

Returns boolean.

Returns:

  • (Boolean)


213
214
215
216
217
218
219
# File 'lib/ssh_data/certificate.rb', line 213

def allowed_source_address?(address)
  return true if source_address.nil?
  parsed_addr = IPAddr.new(address)
  source_address.any? { |a| a.include?(parsed_addr) }
rescue IPAddr::InvalidAddressError
  return false
end

#force_commandObject

The force-command critical option, if present.

Returns a String or nil.



175
176
177
178
179
180
181
182
# File 'lib/ssh_data/certificate.rb', line 175

def force_command
  case value = critical_options[CRITICAL_OPTION_FORCE_COMMAND]
  when String, NilClass
    value
  else
    raise DecodeError, "bad force-request"
  end
end

#openssh(comment: nil) ⇒ Object

OpenSSH certificate in authorized_keys format (see sshd(8) manual page).

comment - Optional String comment to append.

Returns a String key.



128
129
130
# File 'lib/ssh_data/certificate.rb', line 128

def openssh(comment: nil)
  [algo, Base64.strict_encode64(rfc4253), comment].compact.join(" ")
end

#rfc4253Object

RFC4253 binary encoding of the certificate.

Returns a binary String.



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/ssh_data/certificate.rb', line 135

def rfc4253
  Encoding.encode_fields(
    [:string,  algo],
    [:string,  nonce],
    [:raw,     public_key_without_algo],
    [:uint64,  serial],
    [:uint32,  type],
    [:string,  key_id],
    [:list,    valid_principals],
    [:time,    valid_after],
    [:time,    valid_before],
    [:options, critical_options],
    [:options, extensions],
    [:string,  reserved],
    [:string,  ca_key.rfc4253],
    [:string,  signature],
  )
end

#sign(private_key, algo: nil) ⇒ Object

Sign this certificate with a private key.

private_key - An SSHData::PrivateKey::Base subclass instance. algo: - Optionally specify the signature algorithm to use.

Returns nothing.



160
161
162
163
# File 'lib/ssh_data/certificate.rb', line 160

def sign(private_key, algo: nil)
  @ca_key = private_key.public_key
  @signature = private_key.sign(signed_data, algo: algo)
end

#source_addressObject

The source-address critical option, if present.

Returns an Array of IPAddr instances or nil.



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/ssh_data/certificate.rb', line 187

def source_address
  return @source_address if defined?(@source_address)

  value = critical_options[CRITICAL_OPTION_SOURCE_ADDRESS]

  @source_address = case value
  when String
    value.split(",").map do |str_addr|
      begin
        IPAddr.new(str_addr.strip)
      rescue IPAddr::InvalidAddressError => e
        raise DecodeError, "bad source-address: #{e.message}"
      end
    end
  when NilClass
    nil
  else
    raise DecodeError, "bad source-address"
  end
end

#verifyObject

Verify the certificate’s signature.

Returns boolean.



168
169
170
# File 'lib/ssh_data/certificate.rb', line 168

def verify
  ca_key.verify(signed_data, signature)
end