如何将 id_ed25519-cert.pub 合并到 go ssh 客户端中?

How to incorporate id_ed25519-cert.pub into a go ssh client?

我可以使用两个文件通过 SSH(使用 openssh 客户端)连接到我的服务器:~/.ssh/id_ed25519{,-cert.pub}

debug1: Trying private key: /home/xavier/.ssh/id_ed25519                        
debug1: Authentications that can continue: publickey,keyboard-interactive      
debug1: Offering ED25519-CERT public key: /home/xavier/.ssh/id_ed25519          
debug1: Server accepts key: pkalg ssh-ed25519-cert-v01@openssh.com blen 441    
debug1: sign_and_send_pubkey: no separate private key for certificate "/home/xavier/.ssh/id_ed25519"
debug1: Authentication succeeded (publickey).

我想要一个做同样事情的 go 客户端,但我不知道如何将 id_ed25519-cert.pub 文件合并到 https://godoc.org/golang.org/x/crypto/ssh#example-PublicKeys

的示例中
key, err := ioutil.ReadFile("/home/xavier/.ssh/id_ed25519")
if err != nil {
    log.Fatalf("unable to read private key: %v", err)
}

// Create the Signer for this private key.
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
    log.Fatalf("unable to parse private key: %v", err)
}

config := &ssh.ClientConfig{
    User: "user",
    Auth: []ssh.AuthMethod{
        // Use the PublicKeys method for remote authentication.
        ssh.PublicKeys(signer),
    },
}

// Connect to the remote server and perform the SSH handshake.
client, err := ssh.Dial("tcp", "host.com:22", config)
if err != nil {
    log.Fatalf("unable to connect: %v", err)
}
defer client.Close()

部分问题是我不知道这个文件是什么(PublicKey?证书?),部分问题是即使我知道我也不明白它在这个交换中的作用。

我已确认此文件是必需的:删除它会导致 ssh CLI 失败。

那是一个SSH证书文件,用来实现SSH certificate-based user authentication。这通过在 public 密钥层次结构中检查来自可信证书颁发机构的有效签名来验证用户登录的真实性。与标准的基于 SSH 密钥的身份验证(使用 authorized_keys 个文件)相比,此方法具有多种优势,例如:

  • 控制密钥文件的发布(有权访问 CA 主密钥的人必须签署新证书,而不是用户使用 ssh-keygen 发布自己的证书)
  • 自动密钥文件到期
  • 减少了添加或轮换证书时的管理开销,因为验证证书只需要 CA 的 public 密钥;不再需要为每个主机上的每个用户填充一个 authorized_keys 文件
  • 在与用户的关系发生变化时为证书吊销提供更轻松的支持

假设您使用的是内置 golang.org/x/crypto/ssh 库,您可以通过以下方式实现:

  • 读取并解析您签名的 public 密钥证书以及私钥
  • 从私钥创建签名者
  • 使用读入的 public 密钥和相应的私钥签署者创建证书签署者

OpenSSH public 密钥证书的指定格式类似于 authorized_keys 文件。 Go库的ParseAuthorizedKeys函数会将这个文件和return对应的key解析为ssh.PublicKey接口的实例;对于证书,这具体是 ssh.Certificate 结构的一个实例。

查看代码示例(注意:我在您的 ClientConfig 中添加了一个 HostKeyCallback 以使其连接到测试框 – 但是,它使用 InsecureIgnoreHostKey 检查器,我不建议在生产中使用!)。

package main

import (
    "bytes"
    "io/ioutil"
    "log"

    "golang.org/x/crypto/ssh"
)

func main() {
    key, err := ioutil.ReadFile("/tmp/mycert")
    if err != nil {
        log.Fatalf("unable to read private key: %v", err)
    }

    // Create the Signer for this private key.
    signer, err := ssh.ParsePrivateKey(key)
    if err != nil {
        log.Fatalf("unable to parse private key: %v", err)
    }

    // Load the certificate
    cert, err := ioutil.ReadFile("/tmp/mycert-cert.pub")
    if err != nil {
        log.Fatalf("unable to read certificate file: %v", err)
    }

    pk, _, _, _, err := ssh.ParseAuthorizedKey(cert)
    if err != nil {
        log.Fatalf("unable to parse public key: %v", err)
    }

    certSigner, err := ssh.NewCertSigner(pk.(*ssh.Certificate), signer)
    if err != nil {
        log.Fatalf("failed to create cert signer: %v", err)
    }

    config := &ssh.ClientConfig{
        User: "user",
        Auth: []ssh.AuthMethod{
            // Use the PublicKeys method for remote authentication.
            ssh.PublicKeys(certSigner),
        },
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }

    // Connect to the remote server and perform the SSH handshake.
    client, err := ssh.Dial("tcp", "host.com:22", config)
    if err != nil {
        log.Fatalf("unable to connect: %v", err)
    }
    defer client.Close()
}

如果您想编写一个支持证书和非证书的更通用的连接客户端,您显然需要额外的逻辑来处理其他类型的 public 密钥。正如所写,我希望类型断言 pk.(*ssh.Certificate) 对于非证书 public 密钥文件失败! (实际上,对于非证书连接,您可能 根本不需要 读取 public 密钥。)