Skip to content

Commit

Permalink
Rough as guts implementation of SSH agent support for encrypting only
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam Baxter committed Mar 3, 2024
1 parent 29b68c2 commit d6b81ae
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 43 deletions.
1 change: 1 addition & 0 deletions age.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const streamNonceSize = 16
// The caller must call Close on the WriteCloser when done for the last chunk to
// be encrypted and flushed to dst.
func Encrypt(dst io.Writer, recipients ...Recipient) (io.WriteCloser, error) {
fmt.Println("Got to Encrypt()")
if len(recipients) == 0 {
return nil, errors.New("no recipients specified")
}
Expand Down
133 changes: 93 additions & 40 deletions cmd/age/age.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@ package main
import (
"bufio"
"bytes"
"encoding/base64"
"flag"
"fmt"
"io"
"net"
"os"
"regexp"
"runtime/debug"
"slices"
"strings"

"filippo.io/age"
"filippo.io/age/agessh"
"filippo.io/age/armor"
"filippo.io/age/plugin"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/term"
)

Expand Down Expand Up @@ -107,16 +111,17 @@ func main() {
}

var (
outFlag string
decryptFlag, encryptFlag bool
passFlag, versionFlag, armorFlag bool
recipientFlags multiFlag
recipientsFileFlags multiFlag
identityFlags identityFlags
outFlag string
decryptFlag, encryptFlag, agentFlag bool
passFlag, versionFlag, armorFlag bool
recipientFlags multiFlag
recipientsFileFlags multiFlag
identityFlags identityFlags
)

flag.BoolVar(&versionFlag, "version", false, "print the version")
flag.BoolVar(&decryptFlag, "d", false, "decrypt the input")
flag.BoolVar(&agentFlag, "g", true, "use SSH agent")
flag.BoolVar(&decryptFlag, "decrypt", false, "decrypt the input")
flag.BoolVar(&encryptFlag, "e", false, "encrypt the input")
flag.BoolVar(&encryptFlag, "encrypt", false, "encrypt the input")
Expand Down Expand Up @@ -282,7 +287,7 @@ func main() {
case passFlag:
encryptPass(in, out, armorFlag)
default:
encryptNotPass(recipientFlags, recipientsFileFlags, identityFlags, in, out, armorFlag)
encryptNotPass(recipientFlags, recipientsFileFlags, identityFlags, agentFlag, in, out, armorFlag)
}
}

Expand Down Expand Up @@ -314,46 +319,56 @@ func passphrasePromptForEncryption() (string, error) {
return p, nil
}

func encryptNotPass(recs, files []string, identities identityFlags, in io.Reader, out io.Writer, armor bool) {
func encryptNotPass(recs, files []string, identities identityFlags, useAgent bool, in io.Reader, out io.Writer, armor bool) {
fmt.Printf("useAgent is %s\n\n", useAgent)
var recipients []age.Recipient
for _, arg := range recs {
r, err := parseRecipient(arg)
if err, ok := err.(gitHubRecipientError); ok {
errorWithHint(err.Error(), "instead, use recipient files like",
" curl -O https://github.com/"+err.username+".keys",
" age -R "+err.username+".keys")
}
if err != nil {
errorf("%v", err)
}
recipients = append(recipients, r)
}
for _, name := range files {
recs, err := parseRecipientsFile(name)
if err != nil {
errorf("failed to parse recipient file %q: %v", name, err)
}
recipients = append(recipients, recs...)
}
for _, f := range identities {
switch f.Type {
case "i":
ids, err := parseIdentitiesFile(f.Value)
if err != nil {
errorf("reading %q: %v", f.Value, err)
if !useAgent {
for _, arg := range recs {
r, err := parseRecipient(arg)
if err, ok := err.(gitHubRecipientError); ok {
errorWithHint(err.Error(), "instead, use recipient files like",
" curl -O https://github.com/"+err.username+".keys",
" age -R "+err.username+".keys")
}
r, err := identitiesToRecipients(ids)
if err != nil {
errorf("internal error processing %q: %v", f.Value, err)
errorf("%v", err)
}
recipients = append(recipients, r...)
case "j":
id, err := plugin.NewIdentityWithoutData(f.Value, pluginTerminalUI)
recipients = append(recipients, r)
}

for _, name := range files {
recs, err := parseRecipientsFile(name)
if err != nil {
errorf("initializing %q: %v", f.Value, err)
errorf("failed to parse recipient file %q: %v", name, err)
}
recipients = append(recipients, recs...)
}

for _, f := range identities {
switch f.Type {
case "i":
ids, err := parseIdentitiesFile(f.Value)
if err != nil {
errorf("reading %q: %v", f.Value, err)
}
r, err := identitiesToRecipients(ids)
if err != nil {
errorf("internal error processing %q: %v", f.Value, err)
}
recipients = append(recipients, r...)
case "j":
id, err := plugin.NewIdentityWithoutData(f.Value, pluginTerminalUI)
if err != nil {
errorf("initializing %q: %v", f.Value, err)
}
recipients = append(recipients, id.Recipient())
}
recipients = append(recipients, id.Recipient())
}

} else {
fmt.Println("About to run getIdentitiesFromAgent()\n")
recipients, _ = getRecipientFromAgent(recs)
fmt.Printf("Matched %d identities from agent\n", len(recipients))
}
encrypt(recipients, in, out, armor)
}
Expand Down Expand Up @@ -436,6 +451,44 @@ func decryptNotPass(flags identityFlags, in io.Reader, out io.Writer) {
decrypt(identities, in, out)
}

func getRecipientFromAgent(recs []string) ([]age.Recipient, error) {
var ageRecipients []age.Recipient
sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
if err != nil {
fmt.Printf("err running Dial in getIdentitiesFromAgent: %s\n", err)
return nil, nil
}

agent := agent.NewClient(sshAgent)
identities, err := agent.List()
if err != nil {
fmt.Println("Error getting identities")
}

for _, rec := range recs {
fmt.Printf("ID from rec: %v\n", rec)
}
// for _, selectedIdentities := range identiidentitiesFromFlags {
for _, availableIdentity := range identities {
fmt.Printf("ID from agent: %v\n", availableIdentity)
//https://cs.opensource.google/go/x/crypto/+/master:ssh/agent/client.go;l=254
if slices.Contains(recs, base64.StdEncoding.EncodeToString(availableIdentity.Blob)) ||
slices.Contains(recs, availableIdentity.Comment) {

identityString := fmt.Sprintf("%s %s", availableIdentity.Format, base64.StdEncoding.EncodeToString(availableIdentity.Blob))
fmt.Printf("Passing %s to parseIdentites\n", identityString)
i, err := agessh.ParseRecipient(identityString)
if err != nil {
return nil, err
}

ageRecipients = append(ageRecipients, i)
}
}

return ageRecipients, nil
}

func decryptPass(in io.Reader, out io.Writer) {
identities := []age.Identity{
// If there is an scrypt recipient (it will have to be the only one and)
Expand Down
3 changes: 3 additions & 0 deletions cmd/age/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func (gitHubRecipientError) Error() string {
}

func parseRecipient(arg string) (age.Recipient, error) {
fmt.Printf("In parseRecipient, arg was %s\n", arg)
switch {
case strings.HasPrefix(arg, "age1") && strings.Count(arg, "1") > 1:
return plugin.NewRecipient(arg, pluginTerminalUI)
Expand All @@ -46,6 +47,7 @@ func parseRecipient(arg string) (age.Recipient, error) {

func parseRecipientsFile(name string) ([]age.Recipient, error) {
var f *os.File

if name == "-" {
if stdinInUse {
return nil, fmt.Errorf("standard input is used for multiple purposes")
Expand Down Expand Up @@ -92,6 +94,7 @@ func parseRecipientsFile(name string) ([]age.Recipient, error) {
return nil, fmt.Errorf("%q: failed to read recipients file: %v", name, err)
}
if len(recs) == 0 {
fmt.Println("len(recs) == 0 in parseRecipientsFile")
return nil, fmt.Errorf("%q: no recipients found", name)
}
return recs, nil
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ go 1.19

require (
filippo.io/edwards25519 v1.0.0
golang.org/x/crypto v0.4.0
golang.org/x/sys v0.11.0
golang.org/x/term v0.3.0
golang.org/x/crypto v0.20.0
golang.org/x/sys v0.17.0
golang.org/x/term v0.17.0
)

// Test dependencies.
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ github.com/FiloSottile/go-internal v1.8.2-0.20230806172430-94b0f0dc0b1e h1:1pkMK
github.com/FiloSottile/go-internal v1.8.2-0.20230806172430-94b0f0dc0b1e/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
1 change: 1 addition & 0 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func ParseRecipients(f io.Reader) ([]Recipient, error) {
return nil, fmt.Errorf("failed to read recipients file: %v", err)
}
if len(recs) == 0 {
fmt.Println("len(recs) == 0 in ParseRecipients")
return nil, fmt.Errorf("no recipients found")
}
return recs, nil
Expand Down

0 comments on commit d6b81ae

Please sign in to comment.