Skip to content

Add initial support for using ssh keys for signing - #18

Merged
cjslep merged 1 commit into
go-fed:masterfrom
42wim:add-sshsigner
Jun 29, 2020
Merged

cjslep merged 1 commit into
go-fed:masterfrom
42wim:add-sshsigner

Conversation

@42wim

@42wim 42wim commented Jun 14, 2020

Copy link
Copy Markdown
Contributor

This PR builds on #16 and #17 and also fixes #10.

At the moment only ed25519 keys are supported.

The NewSignerFromSSHSigner method takes a ssh.Signer which will be used when actually signing the request/responses.

With this PR you can also use your ssh-agent to sign requests.

Simple example below which will work if you have a ed25519 key in your ssh agent.

package main

import (
        "fmt"
        "net"
        "net/http"
        "os"
        "time"

        "github.com/go-fed/httpsig"
        "golang.org/x/crypto/ssh"
        "golang.org/x/crypto/ssh/agent"
)

func main() {
        sshAgent, _ := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
        ag := agent.NewClient(sshAgent)
        sshsigners, _ := ag.Signers()
        r, _ := http.NewRequest("GET", "http://localhost", nil)
        r.Header.Add("Date", time.Now().String())
        var sshSigner ssh.Signer
        for _, s := range sshsigners {
                if s.PublicKey().Type() != "ssh-ed25519" {
                        continue
                }
                sshSigner = s
                break
        }
        headersToSign := []string{httpsig.RequestTarget, "date"}
        signer, _, _ := httpsig.NewSignerFromSSHSigner(sshSigner, httpsig.DigestSha256, headersToSign, httpsig.Signature, 0)
        // using nil for privatekey
        signer.SignRequest(nil, "pubKeyId", r, nil)
        fmt.Printf("%#v\n", r.Header.Get("Signature"))
}

@cjslep cjslep left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome! I learned a lot reading this review.

Looking at your sample program, I really like the simplicity of it. The httpsig is basically "construct, sign". The only thing that stuck out to me was the comment "using nil for privatekey". I think that's a great warning sign that, somehow that method needs to change so that when a client wants to use a SSHSigner they don't need to pass a nil. We can do that internally though as a shortcut.

I think there's a great opportunity to put SSH Signing on the road to being even more of a first-class supported feature. In that spirit, I have some suggested changes and additions, if you're willing & able to do them. I also include optional bullets if you'd like to extend it to all private key types supported by both SSH & HTTPSignatures, not just ed25519. Those optional bullets don't have to be tackled in this review; I just need someplace to park the vision of fully supporting SSH signing.

What do you think about the following (numbered) adjustments?

  1. Making a type SSHSigner interface dedicated type (so now httpsig.SSHSigner exists)
  2. It has SignRequest and SignResponse similar to Signer but w/o the crypto.PrivateKey parameter. Now this fixes the nil private key parameter being exposed: it no longer exists in the interface.
  3. Now NewSignerFromSSHSigner needs to return a SSHSigner instead of a Signer. It'll do so by passing the buck and calling newSignerFromSSHSigner
    • Optional: Pull out the switch/case statement for the various SSH s.PublicKey().Type() to an Algorithm string (ex: "ssh-ed25519" => ED25519) to be a tiny function, use that tiny function when internally calling newSignerFromSSHSigner.
  4. Now newSignerFromSSHSigner needs to return a SSHSigner. Let's create a new type that simply wraps the existing SignRequest functionality, passes the nil for us, plus hides the existing non-SSH SignRequest for the new type:
type asymmSSHSigner struct {
  *asymmSigner
}
func (a *asymmSSHSigner) SignRequest(pubKeyId string, r *http.Request, body []byte) error {
  return a.asymmSigner.SignRequest(nil, pubKeyId, r, body)
} //etc, similar for SignResponse

var _ SSHSigner = &asymmSSHSigner{} // This compiles
var _ Signer = &asymmSSHSigner{} // This is a compilation error
  1. Wrap the asymmSigner in newSignerFromSSHSigner with asymmSSHSigner.
    • Optional: Do a similar wrapping plus delegation for the algorithms, something like:
type sshAlgorithm struct {
  sshSigner ssh.Signer
  delegate signer // our internal type
}
func (s *sshAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) {
  // Your use of s.sshSigner goes here
}
func (s *sshAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error {
  return s.delegate.Verify(pub, toHash, signature)
}
func (s *sshAlgorithm) String() string {
  return s.delegate.String()
}
  • Optional: This means signerFromSSHSigner can change: get any signer from signerFromString (you got the string from the "tiny-function" in step 3's optional) and then just wrap it with a sshAlgorithm and your sshSigner. No need to hardcode ed25519 in signerFromSSHSigner any longer.

Again, no pressure to do the Optional bullets. The numbered steps I think are worth considering to keep the API for SSH users happy, and welcome your thoughts & feedback!

Comment thread httpsig.go Outdated
Comment thread httpsig.go Outdated
Comment thread signing.go Outdated
@42wim

42wim commented Jun 16, 2020

Copy link
Copy Markdown
Contributor Author

I also thought about doing something like you're suggesting, but didn't do it because it'd wouldn't plug-in as transparent as now (and I thought it would be more easily merged ;-)

But I have no issue with implementing it like you suggest, try to do it later this week.

@42wim
42wim force-pushed the add-sshsigner branch 2 times, most recently from 2840785 to 23b7ccd Compare June 28, 2020 20:42
At the moment only ed25519 keys are supported.
The NewSSHSigner method takes a ssh.Signer which will
be used when actually signing the request/responses.
@42wim

42wim commented Jun 28, 2020

Copy link
Copy Markdown
Contributor Author

@cjslep I've updated the PR with your requested changes, didn't do all the optional ones yet, will be for a next PR.

@42wim
42wim requested a review from cjslep June 28, 2020 20:47

@cjslep cjslep left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome job! Thanks a ton!

@cjslep
cjslep merged commit 10b1029 into go-fed:master Jun 29, 2020
@42wim
42wim deleted the add-sshsigner branch July 1, 2020 22:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Should Support Signing with SSHAgent

2 participants