# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

require "net/ssh"
require "net/ssh/buffer"

# Set the version requirement for when net-ssh should be patched
NET_SSH_PATCH_REQUIREMENT = Gem::Requirement.new(">= 7.0.0", "< 7.2.2")

# This patch provides support for properly loading ECDSA private keys
if NET_SSH_PATCH_REQUIREMENT.satisfied_by?(Gem::Version.new(Net::SSH::Version::STRING))
  Net::SSH::Buffer.class_eval do
    def vagrant_read_private_keyblob(type)
      case type
      when /^ecdsa\-sha2\-(\w*)$/
        curve_name_in_type = $1
        curve_name_in_key = read_string

        unless curve_name_in_type == curve_name_in_key
          raise Net::SSH::Exception, "curve name mismatched (`#{curve_name_in_key}' with `#{curve_name_in_type}')"
        end

        public_key_oct = read_string
        priv_key_bignum = read_bignum
        begin
          curvename = OpenSSL::PKey::EC::CurveNameAlias[curve_name_in_key]
          group = OpenSSL::PKey::EC::Group.new(curvename)
          point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(public_key_oct, 2))
          priv_bn = OpenSSL::BN.new(priv_key_bignum, 2)
          asn1 = OpenSSL::ASN1::Sequence(
            [
              OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(0)),
              OpenSSL::ASN1::Sequence.new(
                [
                  OpenSSL::ASN1::ObjectId("id-ecPublicKey"),
                  OpenSSL::ASN1::ObjectId(curvename)
                ]
              ),
              OpenSSL::ASN1::OctetString.new(
                OpenSSL::ASN1::Sequence.new(
                  [
                    OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(1)),
                    OpenSSL::ASN1::OctetString.new(priv_bn.to_s(2)),
                    OpenSSL::ASN1::ASN1Data.new(
                      [
                        OpenSSL::ASN1::BitString.new(point.to_octet_string(:uncompressed)),
                      ], 1, :CONTEXT_SPECIFIC,
                    )
                  ]
                ).to_der
              )
            ]
          )

          key = OpenSSL::PKey::EC.new(asn1.to_der)

          return key
        rescue OpenSSL::PKey::ECError
          raise NotImplementedError, "unsupported key type `#{type}'"
        end
      else
        netssh_read_private_keyblob(type)
      end
    end

    alias_method :netssh_read_private_keyblob, :read_private_keyblob
    alias_method :read_private_keyblob, :vagrant_read_private_keyblob
  end

  OpenSSL::PKey::EC::Point.class_eval do
    include Net::SSH::Authentication::PubKeyFingerprint
    def to_pem
      "#{ssh_type} #{self.to_bn.to_s(2)}"
    end
  end
end
