Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: eliminate crypto.SignerOpts from Sign/Verify #2636

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 38 additions & 12 deletions libs/httpsignature/ed25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,59 @@ package httpsignature

import (
"crypto"
"crypto/ed25519"
"encoding/hex"
"errors"
"io"
"strconv"

"golang.org/x/crypto/ed25519"
"fmt"
)

// Ed25519PubKey a wrapper type around ed25519.PublicKey to fulfill interface Verifier
type Ed25519PubKey ed25519.PublicKey

// Ed25519PubKey a wrapper type around ed25519.PublicKey to fulfill interface Signator
type Ed25519PrivKey ed25519.PrivateKey

// ED25519-specific signator with access to the corresponding public key
type Ed25519Signator interface {
Signator
Public() Ed25519PubKey
}

// Verify the signature sig for message using the ed25519 public key pk
// Returns true if the signature is valid, false if not and error if the key provided is not valid
func (pk Ed25519PubKey) Verify(message, sig []byte, opts crypto.SignerOpts) (bool, error) {
func (pk Ed25519PubKey) VerifySignature(message, sig []byte) error {
if l := len(pk); l != ed25519.PublicKeySize {
return false, errors.New("ed25519: bad public key length: " + strconv.Itoa(l))
return fmt.Errorf("ed25519: bad public key length: %d", l)
}

return ed25519.Verify(ed25519.PublicKey(pk), message, sig), nil
if !ed25519.Verify(ed25519.PublicKey(pk), message, sig) {
return ErrBadSignature
}
return nil
}

func (pk Ed25519PubKey) String() string {
return hex.EncodeToString(pk)
}

// GenerateEd25519Key generate an ed25519 keypair and return it
func GenerateEd25519Key(rand io.Reader) (Ed25519PubKey, ed25519.PrivateKey, error) {
publicKey, privateKey, err := ed25519.GenerateKey(nil)
return Ed25519PubKey(publicKey), privateKey, err
func (privKey Ed25519PrivKey) SignMessage(
message []byte,
) (signature []byte, err error) {
return ed25519.PrivateKey(privKey).Sign(nil, message, crypto.Hash(0))
}

func (privKey Ed25519PrivKey) Public() Ed25519PubKey {
pubKey := ed25519.PrivateKey(privKey).Public().(ed25519.PublicKey)
return Ed25519PubKey(pubKey)
}

// Get the public key encoded as hexadecimal string
func (privKey Ed25519PrivKey) PublicHex() string {
pubKey := ed25519.PrivateKey(privKey).Public().(ed25519.PublicKey)
return hex.EncodeToString(pubKey)
}

// GenerateEd25519Key generate an ed25519 private key
func GenerateEd25519Key() (Ed25519PrivKey, error) {
_, privateKey, err := ed25519.GenerateKey(nil)
return Ed25519PrivKey(privateKey), err
}
15 changes: 8 additions & 7 deletions libs/httpsignature/hmac.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package httpsignature

import (
"crypto"
"crypto/hmac"
"crypto/sha512"
"crypto/subtle"
"io"
)

// HMACKey is a symmetric key that can be used for HMAC-SHA512 request signing and verification
type HMACKey string

// Sign the message using the hmac key
func (key HMACKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
func (key HMACKey) SignMessage(message []byte) (signature []byte, err error) {
hhash := hmac.New(sha512.New, []byte(key))
// writing the message (HTTP signing string) to it
_, err = hhash.Write(message)
Expand All @@ -24,14 +22,17 @@ func (key HMACKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts)
}

// Verify the signature sig for message using the hmac key
func (key HMACKey) Verify(message, sig []byte, opts crypto.SignerOpts) (bool, error) {
hashSum, err := key.Sign(nil, message, nil)
func (key HMACKey) VerifySignature(message, sig []byte) error {
hashSum, err := key.SignMessage(message)
if err != nil {
return false, err
return err
}
// Return bool by checking whether or not the calculated hash is equal to
// sig pulled out of the header. Check if returned int is equal to 1 to return a bool
return subtle.ConstantTimeCompare(hashSum, sig) == 1, nil
if subtle.ConstantTimeCompare(hashSum, sig) != 1 {
return ErrBadSignature
}
return nil
}

func (key HMACKey) String() string {
Expand Down
40 changes: 18 additions & 22 deletions libs/httpsignature/httpsignature.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import (
"bytes"
"context"
"crypto"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"regexp"
"strings"
Expand All @@ -37,20 +35,21 @@ type signature struct {
// Signator is an interface for cryptographic signature creation
// NOTE that this is a subset of the crypto.Signer interface
type Signator interface {
Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error)
SignMessage(message []byte) (signature []byte, err error)
}

// Verifier is an interface for cryptographic signature verification
type Verifier interface {
Verify(message, sig []byte, opts crypto.SignerOpts) (bool, error)
// Should return ErrBadSignature if the verification fails due to signature
// mismatch.
VerifySignature(message, sig []byte) error
String() string
}

// ParameterizedSignator contains the parameters / options needed to create signatures and a signator
type ParameterizedSignator struct {
SignatureParams
Signator Signator
Opts crypto.SignerOpts
}

// Keystore provides a way to lookup a public key based on the keyID a request was signed with
Expand All @@ -68,7 +67,6 @@ type StaticKeystore struct {
type ParameterizedKeystoreVerifier struct {
SignatureParams
Keystore Keystore
Opts crypto.SignerOpts
}

const (
Expand All @@ -82,6 +80,8 @@ const (

var (
signatureRegex = regexp.MustCompile(`(\w+)="([^"]*)"`)

ErrBadSignature = errors.New("Signature mismatch")
)

// LookupVerifier by returning a static verifier
Expand Down Expand Up @@ -140,7 +140,7 @@ func (sp *SignatureParams) BuildSigningString(req *http.Request) (out []byte, er
if err != nil {
return out, err
}
req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
req.Body = io.NopCloser(bytes.NewBuffer(body))
d.Update(body)
}
req.Header.Add("Digest", d.String())
Expand All @@ -167,13 +167,13 @@ func (sp *SignatureParams) BuildSigningString(req *http.Request) (out []byte, er
return out, nil
}

// Sign the included HTTP request req using signator and options opts
func (sp *SignatureParams) Sign(signator Signator, opts crypto.SignerOpts, req *http.Request) error {
// Sign the included HTTP request req using the signator
func (sp *SignatureParams) SignRequest(signator Signator, req *http.Request) error {
ss, err := sp.BuildSigningString(req)
if err != nil {
return err
}
sig, err := signator.Sign(rand.Reader, ss, opts)
sig, err := signator.SignMessage(ss)
if err != nil {
return err
}
Expand All @@ -190,29 +190,29 @@ func (sp *SignatureParams) Sign(signator Signator, opts crypto.SignerOpts, req *
return nil
}

// SignRequest using signator and options opts in the parameterized signator
// SignRequest using signator in the parameterized signator
func (p *ParameterizedSignator) SignRequest(req *http.Request) error {
return p.SignatureParams.Sign(p.Signator, p.Opts, req)
return p.SignatureParams.SignRequest(p.Signator, req)
}

// Verify the HTTP signature s over HTTP request req using verifier with options opts
func (sp *SignatureParams) Verify(verifier Verifier, opts crypto.SignerOpts, req *http.Request) (bool, error) {
func (sp *SignatureParams) VerifyRequest(verifier Verifier, req *http.Request) error {
signingStr, err := sp.BuildSigningString(req)
if err != nil {
return false, err
return err
}

var tmp signature
err = tmp.UnmarshalText([]byte(req.Header.Get("Signature")))
if err != nil {
return false, err
return err
}

sig, err := base64.StdEncoding.DecodeString(tmp.Sig)
if err != nil {
return false, err
return err
}
return verifier.Verify(signingStr, sig, opts)
return verifier.VerifySignature(signingStr, sig)
}

// VerifyRequest using keystore to lookup verifier with options opts
Expand All @@ -236,13 +236,9 @@ func (pkv *ParameterizedKeystoreVerifier) VerifyRequest(req *http.Request) (cont
sp.Algorithm = pkv.SignatureParams.Algorithm
sp.Headers = pkv.SignatureParams.Headers

valid, err := sp.Verify(verifier, pkv.Opts, req)
if err != nil {
if err := sp.VerifyRequest(verifier, req); err != nil {
return nil, "", err
}
if !valid {
return nil, "", errors.New("signature is not valid")
}

return ctx, sp.KeyID, nil
}
Expand Down
53 changes: 14 additions & 39 deletions libs/httpsignature/httpsignature_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package httpsignature

import (
"crypto"
"encoding/hex"
"net/http"
"reflect"
"testing"

"golang.org/x/crypto/ed25519"
"github.com/stretchr/testify/assert"
)

func TestBuildSigningString(t *testing.T) {
Expand Down Expand Up @@ -48,7 +47,7 @@ func TestBuildSigningString(t *testing.T) {

func TestSign(t *testing.T) {
// ED25519 Test
var privKey ed25519.PrivateKey
var privKey Ed25519PrivKey
privHex := "96aa9ec42242a9a62196281045705196a64e12b15e9160bbb630e38385b82700e7876fd5cc3a228dad634816f4ec4b80a258b2a552467e5d26f30003211bc45d"
privKey, err := hex.DecodeString(privHex)
if err != nil {
Expand All @@ -66,7 +65,7 @@ func TestSign(t *testing.T) {
}
r.Header.Set("Foo", "bar")

err = s.Sign(privKey, crypto.Hash(0), r)
err = s.SignRequest(privKey, r)
if err != nil {
t.Error("Unexpected error while building ED25519 signing string:", err)
}
Expand All @@ -83,7 +82,7 @@ func TestSign(t *testing.T) {

func TestSignRequest(t *testing.T) {
// ED25519 Test
var privKey ed25519.PrivateKey
var privKey Ed25519PrivKey
privHex := "96aa9ec42242a9a62196281045705196a64e12b15e9160bbb630e38385b82700e7876fd5cc3a228dad634816f4ec4b80a258b2a552467e5d26f30003211bc45d"
privKey, err := hex.DecodeString(privHex)
if err != nil {
Expand All @@ -98,7 +97,6 @@ func TestSignRequest(t *testing.T) {
ps := ParameterizedSignator{
SignatureParams: sp,
Signator: privKey,
Opts: crypto.Hash(0),
}

r, err := http.NewRequest("GET", "http://example.org/foo", nil)
Expand Down Expand Up @@ -131,7 +129,6 @@ func TestSignRequest(t *testing.T) {
ps2 := ParameterizedSignator{
SignatureParams: sp2,
Signator: HMACKey(privHex),
Opts: crypto.Hash(0),
}

r2, reqErr := http.NewRequest("GET", "http://example.org/foo2", nil)
Expand Down Expand Up @@ -178,24 +175,14 @@ func TestVerify(t *testing.T) {
r.Header.Set("Foo", "bar")
r.Header.Set("Signature", `keyId="primary",algorithm="ed25519",headers="digest",signature="`+s.Sig+`"`)

valid, err := s.Verify(pubKey, crypto.Hash(0), r)
if err != nil {
t.Error("Unexpected error while building signing string")
}
if !valid {
t.Error("The signature should be valid")
}
err = s.VerifyRequest(pubKey, r)
assert.NoError(t, err)

s.Sig = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
r.Header.Set("Signature", `keyId="primary",algorithm="ed25519",headers="digest",signature="`+s.Sig+`"`)

valid, err = s.Verify(pubKey, crypto.Hash(0), r)
if err != nil {
t.Error("Unexpected error while building signing string")
}
if valid {
t.Error("The signature should be invalid")
}
err = s.VerifyRequest(pubKey, r)
assert.ErrorIs(t, err, ErrBadSignature)

var hmacVerifier HMACKey = "yyqz64U$eG?eUAp24Pm!Fn!Cn"
var s2 signature
Expand All @@ -212,24 +199,14 @@ func TestVerify(t *testing.T) {
req.Header.Set("Foo", "bar")
req.Header.Set("Signature", `keyId="secondary",algorithm="hs2019",headers="digest",signature="`+sig+`"`)

valid, err = s2.Verify(hmacVerifier, nil, req)
if err != nil {
t.Error("Unexpected error while building signing string:", err)
}
if !valid {
t.Error("The signature should be valid")
}
err = s2.VerifyRequest(hmacVerifier, req)
assert.NoError(t, err)

sig = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
req.Header.Set("Signature", `keyId="secondary",algorithm="hs2019",headers="digest",signature="`+sig+`"`)

valid, err = s2.Verify(hmacVerifier, nil, req)
if err != nil {
t.Error("Unexpected error while building signing string")
}
if valid {
t.Error("The signature should be invalid")
}
err = s2.VerifyRequest(hmacVerifier, req)
assert.ErrorIs(t, err, ErrBadSignature)
}

func TestVerifyRequest(t *testing.T) {
Expand All @@ -247,7 +224,6 @@ func TestVerifyRequest(t *testing.T) {
pkv := ParameterizedKeystoreVerifier{
SignatureParams: sp,
Keystore: &StaticKeystore{pubKey},
Opts: crypto.Hash(0),
}

sig := "RbGSX1MttcKCpCkq9nsPGkdJGUZsAU+0TpiXJYkwde+0ZwxEp9dXO3v17DwyGLXjv385253RdGI7URbrI7J6DQ=="
Expand Down Expand Up @@ -288,7 +264,6 @@ func TestVerifyRequest(t *testing.T) {
pkv2 := ParameterizedKeystoreVerifier{
SignatureParams: sp2,
Keystore: &StaticKeystore{hmacVerifier},
Opts: crypto.Hash(0),
}

sig = "3RCLz6TH2I32nj1NY5YaUWDSCNPiKsAVIXjX4merDeNvrGondy7+f3sWQQJWRwEo90FCrthWrrVcgHqqFevS9Q=="
Expand Down Expand Up @@ -402,7 +377,7 @@ func TestTextUnmarshal(t *testing.T) {
}

func TestSignatureParamsFromRequest(t *testing.T) {
var privKey ed25519.PrivateKey
var privKey Ed25519PrivKey
privHex := "96aa9ec42242a9a62196281045705196a64e12b15e9160bbb630e38385b82700e7876fd5cc3a228dad634816f4ec4b80a258b2a552467e5d26f30003211bc45d"
privKey, err := hex.DecodeString(privHex)
if err != nil {
Expand All @@ -420,7 +395,7 @@ func TestSignatureParamsFromRequest(t *testing.T) {
}
r.Header.Set("Foo", "bar")

err = s.Sign(privKey, crypto.Hash(0), r)
err = s.SignRequest(privKey, r)
if err != nil {
t.Error("Unexpected error while building ED25519 signing string:", err)
}
Expand Down
Loading
Loading