Skip to content

Commit

Permalink
move functions into ssh_parse.go
Browse files Browse the repository at this point in the history
Signed-off-by: haoqixu <[email protected]>
  • Loading branch information
haoqixu committed Jan 2, 2025
1 parent 823d1df commit 1af2314
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 64 deletions.
64 changes: 0 additions & 64 deletions age/keysource.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"github.com/sirupsen/logrus"

"github.com/getsops/sops/v3/logging"
"golang.org/x/crypto/ssh"
)

const (
Expand Down Expand Up @@ -238,69 +237,6 @@ func (key *MasterKey) TypeToIdentifier() string {
return KeyTypeIdentifier
}

// readPublicKeyFile attempts to read a public key based on the given private
// key path. It assumes the public key is in the same directory, with the same
// name, but with a ".pub" extension. If the public key cannot be read, an
// error is returned.
func readPublicKeyFile(privateKeyPath string) (ssh.PublicKey, error) {
publicKeyPath := privateKeyPath + ".pub"
f, err := os.Open(publicKeyPath)
if err != nil {
return nil, fmt.Errorf("failed to obtain public %q key for %q SSH key: %w", publicKeyPath, privateKeyPath, err)
}
defer f.Close()
contents, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("failed to read %q: %w", publicKeyPath, err)
}
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(contents)
if err != nil {
return nil, fmt.Errorf("failed to parse %q: %w", publicKeyPath, err)
}
return pubKey, nil
}

// parseSSHIdentityFromPrivateKeyFile returns an age.Identity from the given
// private key file. If the private key file is encrypted, it will configure
// the identity to prompt for a passphrase.
func parseSSHIdentityFromPrivateKeyFile(keyPath string) (age.Identity, error) {
keyFile, err := os.Open(keyPath)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
defer keyFile.Close()
contents, err := io.ReadAll(keyFile)
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
id, err := agessh.ParseIdentity(contents)
if sshErr, ok := err.(*ssh.PassphraseMissingError); ok {
pubKey := sshErr.PublicKey
if pubKey == nil {
pubKey, err = readPublicKeyFile(keyPath)
if err != nil {
return nil, err
}
}
passphrasePrompt := func() ([]byte, error) {
pass, err := readPassphrase(fmt.Sprintf("Enter passphrase for %q:", keyPath))
if err != nil {
return nil, fmt.Errorf("could not read passphrase for %q: %v", keyPath, err)
}
return pass, nil
}
i, err := agessh.NewEncryptedSSHIdentity(pubKey, contents, passphrasePrompt)
if err != nil {
return nil, fmt.Errorf("could not create encrypted SSH identity: %w", err)
}
return i, nil
}
if err != nil {
return nil, fmt.Errorf("malformed SSH identity in %q: %w", keyPath, err)
}
return id, nil
}

// loadAgeSSHIdentity attempts to load the age SSH identity based on an SSH
// private key from the SopsAgeSshPrivateKeyFileEnv environment variable. If the
// environment variable is not present, it will fall back to `~/.ssh/id_ed25519`
Expand Down
84 changes: 84 additions & 0 deletions age/ssh_parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// These functions are similar to those in the age project
// https://github.com/FiloSottile/age/blob/v1.0.0/cmd/age/parse.go
//
// Copyright 2021 The age Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in age's LICENSE file at
// https://github.com/FiloSottile/age/blob/v1.0.0/LICENSE
//
// SPDX-License-Identifier: BSD-3-Clause

package age

import (
"fmt"
"io"
"os"

"filippo.io/age"
"filippo.io/age/agessh"
"golang.org/x/crypto/ssh"
)

// readPublicKeyFile attempts to read a public key based on the given private
// key path. It assumes the public key is in the same directory, with the same
// name, but with a ".pub" extension. If the public key cannot be read, an
// error is returned.
func readPublicKeyFile(privateKeyPath string) (ssh.PublicKey, error) {
publicKeyPath := privateKeyPath + ".pub"
f, err := os.Open(publicKeyPath)
if err != nil {
return nil, fmt.Errorf("failed to obtain public %q key for %q SSH key: %w", publicKeyPath, privateKeyPath, err)
}
defer f.Close()
contents, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("failed to read %q: %w", publicKeyPath, err)
}
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(contents)
if err != nil {
return nil, fmt.Errorf("failed to parse %q: %w", publicKeyPath, err)
}
return pubKey, nil
}

// parseSSHIdentityFromPrivateKeyFile returns an age.Identity from the given
// private key file. If the private key file is encrypted, it will configure
// the identity to prompt for a passphrase.
func parseSSHIdentityFromPrivateKeyFile(keyPath string) (age.Identity, error) {
keyFile, err := os.Open(keyPath)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
defer keyFile.Close()
contents, err := io.ReadAll(keyFile)
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
id, err := agessh.ParseIdentity(contents)
if sshErr, ok := err.(*ssh.PassphraseMissingError); ok {
pubKey := sshErr.PublicKey
if pubKey == nil {
pubKey, err = readPublicKeyFile(keyPath)
if err != nil {
return nil, err
}
}
passphrasePrompt := func() ([]byte, error) {
pass, err := readPassphrase(fmt.Sprintf("Enter passphrase for %q:", keyPath))
if err != nil {
return nil, fmt.Errorf("could not read passphrase for %q: %v", keyPath, err)
}
return pass, nil
}
i, err := agessh.NewEncryptedSSHIdentity(pubKey, contents, passphrasePrompt)
if err != nil {
return nil, fmt.Errorf("could not create encrypted SSH identity: %w", err)
}
return i, nil
}
if err != nil {
return nil, fmt.Errorf("malformed SSH identity in %q: %w", keyPath, err)
}
return id, nil
}
1 change: 1 addition & 0 deletions age/tui.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// These functions have been copied from the age project
// https://github.com/FiloSottile/age/blob/v1.0.0/cmd/age/encrypted_keys.go
//
// Copyright 2021 The age Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in age's LICENSE file at
Expand Down

0 comments on commit 1af2314

Please sign in to comment.