-
Notifications
You must be signed in to change notification settings - Fork 5
/
signature.go
144 lines (120 loc) · 3.69 KB
/
signature.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package client
import (
"bytes"
"crypto/ecdsa"
"crypto/sha256"
"encoding/base64"
"fmt"
"regexp"
"github.com/dgrijalva/jwt-go"
"github.com/drausin/libri/libri/common/errors"
"github.com/golang/protobuf/proto"
)
// regex pattern for a base-64 url-encoded string for a 256-bit number
var b64url256bit *regexp.Regexp
func init() {
// base-64 encoded 256-bit values have 43 chars followed by an =
var err error
b64url256bit, err = regexp.Compile(`^[A-Za-z0-9\-_]{43}=$`)
errors.MaybePanic(err)
}
// Claims holds the claims associated with a message signature.
type Claims struct {
// Hash is the base-64-url encoded string of the hash of the message being signed
Hash string `json:"hash"`
}
// Valid returns whether the claim is valid or invalid via an error.
func (c *Claims) Valid() error {
// check that message hash looks like a base-64-url encoded string
if !b64url256bit.MatchString(c.Hash) {
return fmt.Errorf("%v does not look like a base-64-url encoded 32-byte number",
c.Hash)
}
return nil
}
// NewSignatureClaims creates a new SignatureClaims instance with the given message hash.
func NewSignatureClaims(hash [sha256.Size]byte) *Claims {
return &Claims{
Hash: base64.URLEncoding.EncodeToString(hash[:]),
}
}
// Signer can sign a message.
type Signer interface {
// Sign returns the signature (in the form of an encoded json web token) on the message.
Sign(m proto.Message) (string, error)
}
type emptySigner struct{}
func (emptySigner) Sign(m proto.Message) (string, error) {
return "", nil
}
// NewEmptySigner returns a Signer than returns an empty string signature instead of actually
// signing the message.
func NewEmptySigner() Signer {
return emptySigner{}
}
type ecdsaSigner struct {
key *ecdsa.PrivateKey
}
// NewECDSASigner returns a new Signer instance using the given private key.
func NewECDSASigner(key *ecdsa.PrivateKey) Signer {
return &ecdsaSigner{key}
}
func (s *ecdsaSigner) Sign(m proto.Message) (string, error) {
hash, err := hashMessage(m)
if err != nil {
return "", err
}
// create token
token := jwt.NewWithClaims(jwt.SigningMethodES256, NewSignatureClaims(hash))
// sign with key, yield encoded token string like XXXXXX.YYYYYY.ZZZZZZ
return token.SignedString(s.key)
}
// Verifier verifies the signature on a message.
type Verifier interface {
// Verify verifies that the encoded token is well formed and has been signed by the peer.
Verify(encToken string, fromPubKey *ecdsa.PublicKey, m proto.Message) error
}
type ecsdaVerifier struct{}
// NewVerifier creates a new Verifier instance.
func NewVerifier() Verifier {
return &ecsdaVerifier{}
}
func (v *ecsdaVerifier) Verify(encToken string, fromPubKey *ecdsa.PublicKey,
m proto.Message) error {
token, err := jwt.ParseWithClaims(encToken, &Claims{}, func(token *jwt.Token) (
interface{}, error) {
return fromPubKey, nil
})
if err != nil {
// received error when parsing claims or verifying signature
return err
}
claims, ok := token.Claims.(*Claims)
if !ok {
return fmt.Errorf("token claims %v are not expected SignatureClaims", token.Claims)
}
return verifyMessageHash(m, claims.Hash)
}
func verifyMessageHash(m proto.Message, encClaimedHash string) error {
messageHash, err := hashMessage(m)
if err != nil {
return err
}
claimedHash, err := base64.URLEncoding.DecodeString(encClaimedHash)
if err != nil {
return err
}
if !bytes.Equal(messageHash[:], claimedHash) {
return fmt.Errorf("token claim hash %064x does not match actual hash %064x",
claimedHash, messageHash[:])
}
return nil
}
func hashMessage(m proto.Message) ([sha256.Size]byte, error) {
buf, err := proto.Marshal(m)
if err != nil {
var empty [sha256.Size]byte
return empty, err
}
return sha256.Sum256(buf), nil
}