Support signing and verifying the Digest header

このコミットが含まれているのは:
Cory Slep 2019-09-06 21:16:36 +02:00
コミット 8d61ea6a9a
5個のファイルの変更161行の追加32行の削除

ファイルの表示

@ -13,6 +13,7 @@ signing of hash schemes. Its goals are:
* Remaining flexible with headers included in the signing string
* Support both HTTP requests and responses
* Explicitly not support known-cryptographically weak algorithms
* Support automatic signing and validating Digest headers
## How to use
@ -25,14 +26,18 @@ Signing a request or response requires creating a new `Signer` and using it:
```
func sign(privateKey crypto.PrivateKey, pubKeyId string, r *http.Request) error {
prefs := []httpsig.Algorithm{httpsig.RSA_SHA512, httpsig.RSA_SHA256}
digestAlgorithm := DigestSha256
// The "Date" and "Digest" headers must already be set on r, as well as r.URL.
headersToSign := []string{httpsig.RequestTarget, "date", "digest"}
signer, chosenAlgo, err := httpsig.NewSigner(prefs, headersToSign, httpsig.Signature)
signer, chosenAlgo, err := httpsig.NewSigner(prefs, digestAlgorithm, headersToSign, httpsig.Signature)
if err != nil {
return err
}
// To sign the digest, we need to give the signer a copy of the body...
// ...but it is optional, no digest will be signed if given "nil"
body := ...
// If r were a http.ResponseWriter, call SignResponse instead.
return signer.SignRequest(privateKey, pubKeyId, r)
return signer.SignRequest(privateKey, pubKeyId, r, body)
}
```
@ -51,7 +56,10 @@ func (s *server) handlerFunc(w http.ResponseWriter, r *http.Request) {
// Set headers and such on w
s.mu.Lock()
defer s.mu.Unlock()
err := s.signer.SignResponse(privateKey, pubKeyId, w)
// To sign the digest, we need to give the signer a copy of the response body...
// ...but it is optional, no digest will be signed if given "nil"
body := ...
err := s.signer.SignResponse(privateKey, pubKeyId, w, body)
if err != nil {
...
}
@ -76,6 +84,7 @@ func verify(r *http.Request) error {
pubKeyId := verifier.KeyId()
var algo httpsig.Algorithm = ...
var pubKey crypto.PublicKey = ...
// The verifier will verify the Digest in addition to the HTTP signature
return verifier.Verify(pubKey, algo)
}
```

ファイルの表示

@ -62,6 +62,27 @@ func addDigest(r *http.Request, algo DigestAlgorithm, b []byte) (err error) {
return
}
func addDigestResponse(r http.ResponseWriter, algo DigestAlgorithm, b []byte) (err error) {
_, ok := r.Header()[digestHeader]
if ok {
err = fmt.Errorf("cannot add Digest: Digest is already set")
return
}
var h hash.Hash
var a DigestAlgorithm
h, a, err = getHash(algo)
if err != nil {
return
}
sum := h.Sum(b)
r.Header().Add(digestHeader,
fmt.Sprintf("%s%s%s",
a,
digestDelim,
base64.StdEncoding.EncodeToString(sum[:])))
return
}
func verifyDigest(r *http.Request, body *bytes.Buffer) (err error) {
d := r.Header.Get(digestHeader)
if len(d) == 0 {

ファイルの表示

@ -107,7 +107,14 @@ type Signer interface {
// is expected to be of type []byte. If the Signer was created using an
// RSA based algorithm, then the private key is expected to be of type
// *rsa.PrivateKey.
SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request) error
//
// A Digest (RFC 3230) will be added to the request. The body provided
// must match the body used in the request, and is allowed to be nil.
// The Digest ensures the request body is not tampered with in flight,
// and if the signer is created to also sign the "Digest" header, the
// HTTP Signature will then ensure both the Digest and body are not both
// modified to maliciously represent different content.
SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error
// SignResponse signs the response using a private key. The public key
// id is used by the HTTP client to identify which key to use to verify
// the signature.
@ -116,7 +123,14 @@ type Signer interface {
// is expected to be of type []byte. If the Signer was created using an
// RSA based algorithm, then the private key is expected to be of type
// *rsa.PrivateKey.
SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter) error
//
// A Digest (RFC 3230) will be added to the response. The body provided
// must match the body written in the response, and is allowed to be
// nil. The Digest ensures the response body is not tampered with in
// flight, and if the signer is created to also sign the "Digest"
// header, the HTTP Signature will then ensure both the Digest and body
// are not both modified to maliciously represent different content.
SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error
}
// NewSigner creates a new Signer with the provided algorithm preferences to
@ -125,20 +139,23 @@ type Signer interface {
// algorithms were available, then the default algorithm is used. The headers
// specified will be included into the HTTP signatures.
//
// The Digest will also be calculated on a request's body using the provided
// digest algorithm, if "Digest" is one of the headers listed.
//
// The provided scheme determines which header is populated with the HTTP
// Signature.
//
// An error is returned if an unknown or a known cryptographically insecure
// Algorithm is provided.
func NewSigner(prefs []Algorithm, headers []string, scheme SignatureScheme) (Signer, Algorithm, error) {
func NewSigner(prefs []Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme) (Signer, Algorithm, error) {
for _, pref := range prefs {
s, err := newSigner(pref, headers, scheme)
s, err := newSigner(pref, dAlgo, headers, scheme)
if err != nil {
continue
}
return s, pref, err
}
s, err := newSigner(defaultAlgorithm, headers, scheme)
s, err := newSigner(defaultAlgorithm, dAlgo, headers, scheme)
return s, defaultAlgorithm, err
}
@ -187,11 +204,12 @@ func NewResponseVerifier(r *http.Response) (Verifier, error) {
})
}
func newSigner(algo Algorithm, headers []string, scheme SignatureScheme) (Signer, error) {
func newSigner(algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme) (Signer, error) {
s, err := signerFromString(string(algo))
if err == nil {
a := &asymmSigner{
s: s,
dAlgo: dAlgo,
headers: headers,
targetHeader: scheme,
prefix: scheme.authScheme(),
@ -204,6 +222,7 @@ func newSigner(algo Algorithm, headers []string, scheme SignatureScheme) (Signer
}
c := &macSigner{
m: m,
dAlgo: dAlgo,
headers: headers,
targetHeader: scheme,
prefix: scheme.authScheme(),

ファイルの表示

@ -29,7 +29,9 @@ const (
type httpsigTest struct {
name string
prefs []Algorithm
digestAlg DigestAlgorithm
headers []string
body []byte
scheme SignatureScheme
privKey crypto.PrivateKey
pubKey crypto.PublicKey
@ -37,6 +39,7 @@ type httpsigTest struct {
expectedAlgorithm Algorithm
expectErrorSigningResponse bool
expectRequestPath bool
expectedDigest string
}
var (
@ -62,6 +65,7 @@ func init() {
{
name: "rsa signature",
prefs: []Algorithm{RSA_SHA512},
digestAlg: DigestSha256,
headers: []string{"Date", "Digest"},
scheme: Signature,
privKey: privKey,
@ -69,9 +73,23 @@ func init() {
pubKeyId: "pubKeyId",
expectedAlgorithm: RSA_SHA512,
},
{
name: "digest on rsa signature",
prefs: []Algorithm{RSA_SHA512},
digestAlg: DigestSha256,
headers: []string{"Date", "Digest"},
body: []byte("Last night as I lay dreaming This strangest kind of feeling Revealed its secret meaning And now I know..."),
scheme: Signature,
privKey: privKey,
pubKey: privKey.Public(),
pubKeyId: "pubKeyId",
expectedAlgorithm: RSA_SHA512,
expectedDigest: "SHA-256=TGFzdCBuaWdodCBhcyBJIGxheSBkcmVhbWluZyBUaGlzIHN0cmFuZ2VzdCBraW5kIG9mIGZlZWxpbmcgUmV2ZWFsZWQgaXRzIHNlY3JldCBtZWFuaW5nIEFuZCBub3cgSSBrbm93Li4u47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
},
{
name: "hmac signature",
prefs: []Algorithm{HMAC_SHA256},
digestAlg: DigestSha256,
headers: []string{"Date", "Digest"},
scheme: Signature,
privKey: macKey,
@ -79,9 +97,23 @@ func init() {
pubKeyId: "pubKeyId",
expectedAlgorithm: HMAC_SHA256,
},
{
name: "digest on hmac signature",
prefs: []Algorithm{HMAC_SHA256},
digestAlg: DigestSha256,
headers: []string{"Date", "Digest"},
body: []byte("I've never ever been to paradise I've never ever seen no angel's eyes You'll never ever let this magic die No matter where you are, you are my lucky star."),
scheme: Signature,
privKey: macKey,
pubKey: macKey,
pubKeyId: "pubKeyId",
expectedAlgorithm: HMAC_SHA256,
expectedDigest: "SHA-256=SSd2ZSBuZXZlciBldmVyIGJlZW4gdG8gcGFyYWRpc2UgSSd2ZSBuZXZlciBldmVyIHNlZW4gbm8gYW5nZWwncyBleWVzIFlvdSdsbCBuZXZlciBldmVyIGxldCB0aGlzIG1hZ2ljIGRpZSBObyBtYXR0ZXIgd2hlcmUgeW91IGFyZSwgeW91IGFyZSBteSBsdWNreSBzdGFyLuOwxEKY/BwUmvv0yJlvuSQnrkHkZJuTTKSVmRt4UrhV",
},
{
name: "rsa authorization",
prefs: []Algorithm{RSA_SHA512},
digestAlg: DigestSha256,
headers: []string{"Date", "Digest"},
scheme: Authorization,
privKey: privKey,
@ -92,6 +124,7 @@ func init() {
{
name: "hmac authorization",
prefs: []Algorithm{HMAC_SHA256},
digestAlg: DigestSha256,
headers: []string{"Date", "Digest"},
scheme: Authorization,
privKey: macKey,
@ -101,6 +134,7 @@ func init() {
},
{
name: "default algo",
digestAlg: DigestSha256,
headers: []string{"Date", "Digest"},
scheme: Signature,
privKey: privKey,
@ -111,6 +145,7 @@ func init() {
{
name: "default headers",
prefs: []Algorithm{RSA_SHA512},
digestAlg: DigestSha256,
scheme: Signature,
privKey: privKey,
pubKey: privKey.Public(),
@ -120,6 +155,7 @@ func init() {
{
name: "different pub key id",
prefs: []Algorithm{RSA_SHA512},
digestAlg: DigestSha256,
headers: []string{"Date", "Digest"},
scheme: Signature,
privKey: privKey,
@ -130,6 +166,7 @@ func init() {
{
name: "with request target",
prefs: []Algorithm{RSA_SHA512},
digestAlg: DigestSha256,
headers: []string{"Date", "Digest", RequestTarget},
scheme: Signature,
privKey: privKey,
@ -168,7 +205,7 @@ func toHeaderSignatureParameters(k string, vals []string) string {
func TestSignerRequest(t *testing.T) {
testFn := func(t *testing.T, test httpsigTest) {
s, a, err := NewSigner(test.prefs, test.headers, test.scheme)
s, a, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme)
if err != nil {
t.Fatalf("%s", err)
}
@ -181,8 +218,10 @@ func TestSignerRequest(t *testing.T) {
t.Fatalf("%s", err)
}
req.Header.Set("Date", testDate)
req.Header.Set("Digest", testDigest)
err = s.SignRequest(test.privKey, test.pubKeyId, req)
if test.body == nil {
req.Header.Set("Digest", testDigest)
}
err = s.SignRequest(test.privKey, test.pubKeyId, req, test.body)
if err != nil {
t.Fatalf("%s", err)
}
@ -201,6 +240,8 @@ func TestSignerRequest(t *testing.T) {
t.Fatalf("%s\ndoes not contain\n%s", vals[0], p)
} else if !strings.Contains(vals[0], signatureParameter) {
t.Fatalf("%s\ndoes not contain\n%s", vals[0], signatureParameter)
} else if test.body != nil && req.Header.Get("Digest") != test.expectedDigest {
t.Fatalf("%s\ndoes not match\n%s", req.Header.Get("Digest"), test.expectedDigest)
}
// For schemes with an authScheme, enforce its is present and at the beginning
if len(test.scheme.authScheme()) > 0 {
@ -218,12 +259,14 @@ func TestSignerRequest(t *testing.T) {
func TestSignerResponse(t *testing.T) {
testFn := func(t *testing.T, test httpsigTest) {
s, _, err := NewSigner(test.prefs, test.headers, test.scheme)
s, _, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme)
// Test response signing
resp := httptest.NewRecorder()
resp.HeaderMap.Set("Date", testDate)
resp.HeaderMap.Set("Digest", testDigest)
err = s.SignResponse(test.privKey, test.pubKeyId, resp)
if test.body == nil {
resp.HeaderMap.Set("Digest", testDigest)
}
err = s.SignResponse(test.privKey, test.pubKeyId, resp, test.body)
if test.expectErrorSigningResponse {
if err != nil {
// Skip rest of testing
@ -247,6 +290,8 @@ func TestSignerResponse(t *testing.T) {
t.Fatalf("%s\ndoes not contain\n%s", vals[0], p)
} else if !strings.Contains(vals[0], signatureParameter) {
t.Fatalf("%s\ndoes not contain\n%s", vals[0], signatureParameter)
} else if test.body != nil && resp.Header().Get("Digest") != test.expectedDigest {
t.Fatalf("%s\ndoes not match\n%s", resp.Header().Get("Digest"), test.expectedDigest)
}
// For schemes with an authScheme, enforce its is present and at the beginning
if len(test.scheme.authScheme()) > 0 {
@ -266,6 +311,7 @@ func TestNewSignerRequestMissingHeaders(t *testing.T) {
failingTests := []struct {
name string
prefs []Algorithm
digestAlg DigestAlgorithm
headers []string
scheme SignatureScheme
privKey crypto.PrivateKey
@ -275,6 +321,7 @@ func TestNewSignerRequestMissingHeaders(t *testing.T) {
{
name: "wants digest",
prefs: []Algorithm{RSA_SHA512},
digestAlg: DigestSha256,
headers: []string{"Date", "Digest"},
scheme: Signature,
privKey: privKey,
@ -285,7 +332,7 @@ func TestNewSignerRequestMissingHeaders(t *testing.T) {
for _, test := range failingTests {
t.Run(test.name, func(t *testing.T) {
test := test
s, a, err := NewSigner(test.prefs, test.headers, test.scheme)
s, a, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme)
if err != nil {
t.Fatalf("%s", err)
}
@ -297,7 +344,7 @@ func TestNewSignerRequestMissingHeaders(t *testing.T) {
t.Fatalf("%s", err)
}
req.Header.Set("Date", testDate)
err = s.SignRequest(test.privKey, test.pubKeyId, req)
err = s.SignRequest(test.privKey, test.pubKeyId, req, nil)
if err == nil {
t.Fatalf("expect error but got nil")
}
@ -309,6 +356,7 @@ func TestNewSignerResponseMissingHeaders(t *testing.T) {
failingTests := []struct {
name string
prefs []Algorithm
digestAlg DigestAlgorithm
headers []string
scheme SignatureScheme
privKey crypto.PrivateKey
@ -319,6 +367,7 @@ func TestNewSignerResponseMissingHeaders(t *testing.T) {
{
name: "want digest",
prefs: []Algorithm{RSA_SHA512},
digestAlg: DigestSha256,
headers: []string{"Date", "Digest"},
scheme: Signature,
privKey: privKey,
@ -329,7 +378,7 @@ func TestNewSignerResponseMissingHeaders(t *testing.T) {
for _, test := range failingTests {
t.Run(test.name, func(t *testing.T) {
test := test
s, a, err := NewSigner(test.prefs, test.headers, test.scheme)
s, a, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme)
if err != nil {
t.Fatalf("%s", err)
}
@ -339,7 +388,7 @@ func TestNewSignerResponseMissingHeaders(t *testing.T) {
resp := httptest.NewRecorder()
resp.HeaderMap.Set("Date", testDate)
resp.HeaderMap.Set("Digest", testDigest)
err = s.SignResponse(test.privKey, test.pubKeyId, resp)
err = s.SignResponse(test.privKey, test.pubKeyId, resp, nil)
if err != nil {
t.Fatalf("expected error, got nil")
}
@ -357,12 +406,14 @@ func TestNewVerifier(t *testing.T) {
t.Fatalf("%s", err)
}
req.Header.Set("Date", testDate)
req.Header.Set("Digest", testDigest)
s, _, err := NewSigner(test.prefs, test.headers, test.scheme)
if test.body == nil {
req.Header.Set("Digest", testDigest)
}
s, _, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme)
if err != nil {
t.Fatalf("%s", err)
}
err = s.SignRequest(test.privKey, test.pubKeyId, req)
err = s.SignRequest(test.privKey, test.pubKeyId, req, test.body)
if err != nil {
t.Fatalf("%s", err)
}
@ -392,12 +443,14 @@ func TestNewResponseVerifier(t *testing.T) {
// Prepare
resp := httptest.NewRecorder()
resp.HeaderMap.Set("Date", testDate)
resp.HeaderMap.Set("Digest", testDigest)
s, _, err := NewSigner(test.prefs, test.headers, test.scheme)
if test.body == nil {
resp.HeaderMap.Set("Digest", testDigest)
}
s, _, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme)
if err != nil {
t.Fatalf("%s", err)
}
err = s.SignResponse(test.privKey, test.pubKeyId, resp)
err = s.SignResponse(test.privKey, test.pubKeyId, resp, test.body)
if err != nil {
t.Fatalf("%s", err)
}
@ -465,12 +518,12 @@ func Test_Signing_HTTP_Messages_AppendixC(t *testing.T) {
r.Header["Content-Type"] = []string{"application/json"}
setDigest(r)
s, _, err := NewSigner([]Algorithm{RSA_SHA256}, test.headers, Authorization)
s, _, err := NewSigner([]Algorithm{RSA_SHA256}, DigestSha256, test.headers, Authorization)
if err != nil {
t.Fatalf("error creating signer: %s", err)
}
if err := s.SignRequest(testSpecRSAPrivateKey, "Test", r); err != nil {
if err := s.SignRequest(testSpecRSAPrivateKey, "Test", r, nil); err != nil {
t.Fatalf("error signing request: %s", err)
}

ファイルの表示

@ -40,12 +40,20 @@ var _ Signer = &macSigner{}
type macSigner struct {
m macer
makeDigest bool
dAlgo DigestAlgorithm
headers []string
targetHeader SignatureScheme
prefix string
}
func (m *macSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request) error {
func (m *macSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error {
if body != nil {
err := addDigest(r, m.dAlgo, body)
if err != nil {
return err
}
}
s, err := m.signatureString(r)
if err != nil {
return err
@ -58,7 +66,13 @@ func (m *macSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http
return nil
}
func (m *macSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter) error {
func (m *macSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error {
if body != nil {
err := addDigestResponse(r, m.dAlgo, body)
if err != nil {
return err
}
}
s, err := m.signatureStringResponse(r)
if err != nil {
return err
@ -96,17 +110,24 @@ var _ Signer = &asymmSigner{}
type asymmSigner struct {
s signer
makeDigest bool
dAlgo DigestAlgorithm
headers []string
targetHeader SignatureScheme
prefix string
}
func (a *asymmSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request) error {
func (a *asymmSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error {
if body != nil {
err := addDigest(r, a.dAlgo, body)
if err != nil {
return err
}
}
s, err := a.signatureString(r)
if err != nil {
return err
}
enc, err := a.signSignature(pKey, s)
if err != nil {
return err
@ -115,7 +136,13 @@ func (a *asymmSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *ht
return nil
}
func (a *asymmSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter) error {
func (a *asymmSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error {
if body != nil {
err := addDigestResponse(r, a.dAlgo, body)
if err != nil {
return err
}
}
s, err := a.signatureStringResponse(r)
if err != nil {
return err