class SSHData::Certificate

Constants

ALGOS
ALGO_DSA
ALGO_ECDSA256
ALGO_ECDSA384
ALGO_ECDSA521
ALGO_ED25519
ALGO_RSA

Certificate algorithm identifiers

ALGO_SKECDSA256
ALGO_SKED25519
BEGINNING_OF_TIME

Special values for valid_before and valid_after.

CRITICAL_OPTION_FORCE_COMMAND
CRITICAL_OPTION_SOURCE_ADDRESS
END_OF_TIME
TYPE_HOST
TYPE_USER

Integer certificate types

Attributes

algo[R]
ca_key[R]
critical_options[R]
extensions[R]
key_id[R]
nonce[R]
public_key[R]
reserved[R]
serial[R]
signature[R]
type[R]
valid_after[R]
valid_before[R]
valid_principals[R]

Public Class Methods

new(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: "") click to toggle source

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.

# 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
parse_openssh(cert, unsafe_no_verify: false) click to toggle source

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.

# 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) click to toggle source

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.

# 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

Public Instance Methods

allowed_source_address?(address) click to toggle source

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

address - A String IP address.

Returns boolean.

# 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_command() click to toggle source

The force-command critical option, if present.

Returns a String or nil.

# 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) click to toggle source

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

comment - Optional String comment to append.

Returns a String key.

# File lib/ssh_data/certificate.rb, line 128
def openssh(comment: nil)
  [algo, Base64.strict_encode64(rfc4253), comment].compact.join(" ")
end
rfc4253() click to toggle source

RFC4253 binary encoding of the certificate.

Returns a binary String.

# 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) click to toggle source

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.

# 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_address() click to toggle source

The source-address critical option, if present.

Returns an Array of IPAddr instances or nil.

# 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
verify() click to toggle source

Verify the certificate’s signature.

Returns boolean.

# File lib/ssh_data/certificate.rb, line 168
def verify
  ca_key.verify(signed_data, signature)
end

Private Instance Methods

public_key_without_algo() click to toggle source

Helper for getting the RFC4253 encoded public key with the first field (the algorithm) stripped off.

Returns a String.

# File lib/ssh_data/certificate.rb, line 235
def public_key_without_algo
  key = public_key.rfc4253
  _, algo_len = Encoding.decode_string(key)
  key.byteslice(algo_len..-1)
end
signed_data() click to toggle source

The portion of the certificate over which the signature is calculated.

Returns a binary String.

# File lib/ssh_data/certificate.rb, line 226
def signed_data
  siglen = self.signature.bytesize + 4
  rfc4253.byteslice(0...-siglen)
end