Skip to content

Commit 10b1029

Browse files
authored
Merge pull request #18 from 42wim/add-sshsigner
Add initial support for using ssh keys for signing
2 parents 7fd2c4c + 03551c9 commit 10b1029

5 files changed

Lines changed: 132 additions & 4 deletions

File tree

algorithms.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"golang.org/x/crypto/ed25519"
2222
"golang.org/x/crypto/ripemd160"
2323
"golang.org/x/crypto/sha3"
24+
"golang.org/x/crypto/ssh"
2425
)
2526

2627
const (
@@ -219,9 +220,19 @@ func (r *rsaAlgorithm) String() string {
219220

220221
var _ signer = &ed25519Algorithm{}
221222

222-
type ed25519Algorithm struct{}
223+
type ed25519Algorithm struct {
224+
sshSigner ssh.Signer
225+
}
223226

224227
func (r *ed25519Algorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) {
228+
if r.sshSigner != nil {
229+
sshsig, err := r.sshSigner.Sign(rand, sig)
230+
if err != nil {
231+
return nil, err
232+
}
233+
234+
return sshsig.Blob, nil
235+
}
225236
ed25519K, ok := p.(ed25519.PrivateKey)
226237
if !ok {
227238
return nil, errors.New("crypto.PrivateKey is not ed25519.PrivateKey")
@@ -418,6 +429,15 @@ func newAlgorithm(algo string, key []byte) (hash.Hash, crypto.Hash, error) {
418429
return h, c, err
419430
}
420431

432+
func signerFromSSHSigner(sshSigner ssh.Signer, s string) (signer, error) {
433+
if !strings.HasPrefix(s, ed25519Prefix) {
434+
return nil, fmt.Errorf("no signer matching %q", s)
435+
}
436+
return &ed25519Algorithm{
437+
sshSigner: sshSigner,
438+
}, nil
439+
}
440+
421441
// signerFromString is an internally public method constructor
422442
func signerFromString(s string) (signer, error) {
423443
s = strings.ToLower(s)

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module github.com/go-fed/httpsig
22

3-
require golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
3+
require golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
44

55
go 1.13

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
2-
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
3-
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
2+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
3+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
44
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
55
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
66
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=

httpsig.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"fmt"
1313
"net/http"
1414
"time"
15+
16+
"golang.org/x/crypto/ssh"
1517
)
1618

1719
// Algorithm specifies a cryptography secure algorithm for signing HTTP requests
@@ -170,6 +172,70 @@ func NewSigner(prefs []Algorithm, dAlgo DigestAlgorithm, headers []string, schem
170172
return s, defaultAlgorithm, err
171173
}
172174

175+
// Signers will sign HTTP requests or responses based on the algorithms and
176+
// headers selected at creation time.
177+
//
178+
// Signers are not safe to use between multiple goroutines.
179+
//
180+
// Note that signatures do set the deprecated 'algorithm' parameter for
181+
// backwards compatibility.
182+
type SSHSigner interface {
183+
// SignRequest signs the request using ssh.Signer.
184+
// The public key id is used by the HTTP server to identify which key to use
185+
// to verify the signature.
186+
//
187+
// A Digest (RFC 3230) will be added to the request. The body provided
188+
// must match the body used in the request, and is allowed to be nil.
189+
// The Digest ensures the request body is not tampered with in flight,
190+
// and if the signer is created to also sign the "Digest" header, the
191+
// HTTP Signature will then ensure both the Digest and body are not both
192+
// modified to maliciously represent different content.
193+
SignRequest(pubKeyId string, r *http.Request, body []byte) error
194+
// SignResponse signs the response using ssh.Signer. The public key
195+
// id is used by the HTTP client to identify which key to use to verify
196+
// the signature.
197+
//
198+
// A Digest (RFC 3230) will be added to the response. The body provided
199+
// must match the body written in the response, and is allowed to be
200+
// nil. The Digest ensures the response body is not tampered with in
201+
// flight, and if the signer is created to also sign the "Digest"
202+
// header, the HTTP Signature will then ensure both the Digest and body
203+
// are not both modified to maliciously represent different content.
204+
SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error
205+
}
206+
207+
// NewwSSHSigner creates a new Signer using the specified ssh.Signer
208+
// At the moment only ed25519 ssh keys are supported.
209+
// The headers specified will be included into the HTTP signatures.
210+
//
211+
// The Digest will also be calculated on a request's body using the provided
212+
// digest algorithm, if "Digest" is one of the headers listed.
213+
//
214+
// The provided scheme determines which header is populated with the HTTP
215+
// Signature.
216+
func NewSSHSigner(s ssh.Signer, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, Algorithm, error) {
217+
sshAlgo := getSSHAlgorithm(s.PublicKey().Type())
218+
if sshAlgo == "" {
219+
return nil, "", fmt.Errorf("key type: %s not supported yet.", s.PublicKey().Type())
220+
}
221+
222+
signer, err := newSSHSigner(s, sshAlgo, dAlgo, headers, scheme, expiresIn)
223+
if err != nil {
224+
return nil, "", err
225+
}
226+
227+
return signer, sshAlgo, nil
228+
}
229+
230+
func getSSHAlgorithm(pkType string) Algorithm {
231+
switch pkType {
232+
case "ssh-ed25519":
233+
return ED25519
234+
}
235+
236+
return ""
237+
}
238+
173239
// Verifier verifies HTTP Signatures.
174240
//
175241
// It will determine which of the supported headers has the parameters
@@ -225,6 +291,34 @@ func NewResponseVerifier(r *http.Response) (Verifier, error) {
225291
})
226292
}
227293

294+
func newSSHSigner(sshSigner ssh.Signer, algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, error) {
295+
var expires, created int64 = 0, 0
296+
297+
if expiresIn != 0 {
298+
created = time.Now().Unix()
299+
expires = created + expiresIn
300+
}
301+
302+
s, err := signerFromSSHSigner(sshSigner, string(algo))
303+
if err != nil {
304+
return nil, fmt.Errorf("no crypto implementation available for ssh algo %q", algo)
305+
}
306+
307+
a := &asymmSSHSigner{
308+
asymmSigner: &asymmSigner{
309+
s: s,
310+
dAlgo: dAlgo,
311+
headers: headers,
312+
targetHeader: scheme,
313+
prefix: scheme.authScheme(),
314+
created: created,
315+
expires: expires,
316+
},
317+
}
318+
319+
return a, nil
320+
}
321+
228322
func newSigner(algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, error) {
229323

230324
var expires, created int64 = 0, 0

signing.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,20 @@ func (a *asymmSigner) signatureStringResponse(r http.ResponseWriter) (string, er
179179
return signatureString(r.Header(), a.headers, requestTargetNotPermitted, a.created, a.expires)
180180
}
181181

182+
var _ SSHSigner = &asymmSSHSigner{}
183+
184+
type asymmSSHSigner struct {
185+
*asymmSigner
186+
}
187+
188+
func (a *asymmSSHSigner) SignRequest(pubKeyId string, r *http.Request, body []byte) error {
189+
return a.asymmSigner.SignRequest(nil, pubKeyId, r, body)
190+
}
191+
192+
func (a *asymmSSHSigner) SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error {
193+
return a.asymmSigner.SignResponse(nil, pubKeyId, r, body)
194+
}
195+
182196
func setSignatureHeader(h http.Header, targetHeader, prefix, pubKeyId, algo, enc string, headers []string, created int64, expires int64) {
183197
if len(headers) == 0 {
184198
headers = defaultHeaders

0 commit comments

Comments
 (0)