mcrypto: move SignVerifier code into Secret, to account for encrypting later on
This commit is contained in:
parent
d4c665c4d5
commit
b30ccf4db8
@ -7,12 +7,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO rather than have the NewSignerVerifier methods, it might be better to
|
|
||||||
// have a Secret type, which implements Signer/Verifier. That way when there's
|
|
||||||
// Encrypter/Decrypter interfaces then Secret can implement those too, and
|
|
||||||
// PublicKey/PrivateKey can implement their respective ones. There'll be a nice
|
|
||||||
// symmetry there, rather than having NewEncrypterDecrypter functions.
|
|
||||||
|
|
||||||
// Instead of outputing opaque hex garbage, this package opts to add a prefix to
|
// Instead of outputing opaque hex garbage, this package opts to add a prefix to
|
||||||
// the garbage. Each "type" of string returned has its own character which is
|
// the garbage. Each "type" of string returned has its own character which is
|
||||||
// not found in the hex range (0-9, a-f), and in addition each also has a
|
// not found in the hex range (0-9, a-f), and in addition each also has a
|
||||||
|
84
mcrypto/secret.go
Normal file
84
mcrypto/secret.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package mcrypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mediocregopher/mediocre-go-lib/mlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Secret contains a set of bytes which are inteded to remain secret within some
|
||||||
|
// context (e.g. a backend application keeping a secret from the frontend).
|
||||||
|
//
|
||||||
|
// Secret inherently implements the Signer and Verifier interfaces.
|
||||||
|
//
|
||||||
|
// Secret can be initialized with NewSecret or NewWeakSecret. The Signatures
|
||||||
|
// produced by these will be of differing lengths, but either can Verify a
|
||||||
|
// Signature made by the other as long as the secret bytes they are initialized
|
||||||
|
// with are the same.
|
||||||
|
type Secret struct {
|
||||||
|
sigSize uint8 // in bytes, shouldn't be more than 32, cause sha256
|
||||||
|
secret []byte
|
||||||
|
|
||||||
|
// only used during tests
|
||||||
|
testNow time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSecret initializes and returns an instance of Secret which uses the given
|
||||||
|
// bytes as the underlying secret.
|
||||||
|
func NewSecret(secret []byte) Secret {
|
||||||
|
return Secret{sigSize: 20, secret: secret}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWeakSecret is like NewSecret but the Signatures it produces will be
|
||||||
|
// shorter and weaker (though still secure enough for most applications).
|
||||||
|
// Signatures produced by either normal or weak Secrets can be Verified by the
|
||||||
|
// other.
|
||||||
|
func NewWeakSecret(secret []byte) Secret {
|
||||||
|
return Secret{sigSize: 8, secret: secret}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Secret) now() time.Time {
|
||||||
|
if !s.testNow.IsZero() {
|
||||||
|
return s.testNow
|
||||||
|
}
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Secret) signRaw(
|
||||||
|
r io.Reader,
|
||||||
|
sigLen uint8, salt []byte, t time.Time,
|
||||||
|
) (
|
||||||
|
[]byte, error,
|
||||||
|
) {
|
||||||
|
h := hmac.New(sha256.New, s.secret)
|
||||||
|
r = sigPrefixReader(r, sigLen, salt, t)
|
||||||
|
if _, err := io.Copy(h, r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return h.Sum(nil)[:sigLen], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Secret) sign(r io.Reader) (Signature, error) {
|
||||||
|
salt := make([]byte, 8)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := s.now()
|
||||||
|
sig, err := s.signRaw(r, s.sigSize, salt, t)
|
||||||
|
return Signature{sig: sig, salt: salt, t: t}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Secret) verify(sig Signature, r io.Reader) error {
|
||||||
|
sigB, err := s.signRaw(r, uint8(len(sig.sig)), sig.salt, sig.t)
|
||||||
|
if err != nil {
|
||||||
|
return mlog.ErrWithKV(err, sig)
|
||||||
|
} else if !hmac.Equal(sigB, sig.sig) {
|
||||||
|
return mlog.ErrWithKV(ErrInvalidSig, sig)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
54
mcrypto/secret_test.go
Normal file
54
mcrypto/secret_test.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package mcrypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ansel1/merry"
|
||||||
|
"github.com/mediocregopher/mediocre-go-lib/mtest"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSecretSignVerify(t *T) {
|
||||||
|
secretRaw := mtest.RandBytes(16)
|
||||||
|
secret := NewSecret(secretRaw)
|
||||||
|
weakSecret := NewWeakSecret(secretRaw)
|
||||||
|
var prevStr string
|
||||||
|
var prevSig, prevWeakSig Signature
|
||||||
|
for i := 0; i < 10000; i++ {
|
||||||
|
now := time.Now().Round(0)
|
||||||
|
secret.testNow = now
|
||||||
|
weakSecret.testNow = now
|
||||||
|
|
||||||
|
thisStr := mtest.RandHex(512)
|
||||||
|
thisSig := SignString(secret, thisStr)
|
||||||
|
thisWeakSig := SignString(weakSecret, thisStr)
|
||||||
|
thisSigStr, thisWeakSigStr := thisSig.String(), thisWeakSig.String()
|
||||||
|
|
||||||
|
// sanity checks
|
||||||
|
assert.Equal(t, now, thisSig.Time())
|
||||||
|
assert.Equal(t, now, thisWeakSig.Time())
|
||||||
|
assert.NotEmpty(t, thisSigStr)
|
||||||
|
assert.NotEmpty(t, thisWeakSigStr)
|
||||||
|
assert.NotEqual(t, thisSigStr, thisWeakSigStr)
|
||||||
|
assert.True(t, len(thisSigStr) > len(thisWeakSigStr))
|
||||||
|
|
||||||
|
// Either secret should be able to verify either signature
|
||||||
|
assert.NoError(t, VerifyString(secret, thisSig, thisStr))
|
||||||
|
assert.NoError(t, VerifyString(weakSecret, thisWeakSig, thisStr))
|
||||||
|
assert.NoError(t, VerifyString(secret, thisWeakSig, thisStr))
|
||||||
|
assert.NoError(t, VerifyString(weakSecret, thisSig, thisStr))
|
||||||
|
|
||||||
|
if prevStr != "" {
|
||||||
|
assert.NotEqual(t, prevSig.String(), thisSigStr)
|
||||||
|
assert.NotEqual(t, prevWeakSig.String(), thisWeakSigStr)
|
||||||
|
err := VerifyString(secret, prevSig, thisStr)
|
||||||
|
assert.True(t, merry.Is(err, ErrInvalidSig))
|
||||||
|
err = VerifyString(secret, prevWeakSig, thisStr)
|
||||||
|
assert.True(t, merry.Is(err, ErrInvalidSig))
|
||||||
|
}
|
||||||
|
prevStr = thisStr
|
||||||
|
prevSig = thisSig
|
||||||
|
prevWeakSig = thisWeakSig
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,6 @@ package mcrypto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -187,73 +184,3 @@ func VerifyBytes(v Verifier, s Signature, b []byte) error {
|
|||||||
func VerifyString(v Verifier, s Signature, in string) error {
|
func VerifyString(v Verifier, s Signature, in string) error {
|
||||||
return VerifyBytes(v, s, []byte(in))
|
return VerifyBytes(v, s, []byte(in))
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
type signVerifier struct {
|
|
||||||
outSize uint8 // in bytes, shouldn't be more than 32, cause sha256
|
|
||||||
secret []byte
|
|
||||||
|
|
||||||
// only used during tests
|
|
||||||
testNow time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSignerVerifier returns Signer and Verifier instances which will use the
|
|
||||||
// given secret to sign and verify all Signatures
|
|
||||||
func NewSignerVerifier(secret []byte) (Signer, Verifier) {
|
|
||||||
sv := signVerifier{outSize: 20, secret: secret}
|
|
||||||
return sv, sv
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWeakSignerVerifier returns Signer and Verifier instances, similar to how
|
|
||||||
// NewSignVerifier does. The Signatures generated by this Signer will be smaller
|
|
||||||
// in text size, and therefore weaker, but are still fine for most applications.
|
|
||||||
//
|
|
||||||
// The Verifiers returned by both NewSignVerifier and NewWeakSignVerifier can
|
|
||||||
// verify each-other's signatures, as long as the secret is the same.
|
|
||||||
func NewWeakSignerVerifier(secret []byte) (Signer, Verifier) {
|
|
||||||
sv := signVerifier{outSize: 8, secret: secret}
|
|
||||||
return sv, sv
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv signVerifier) now() time.Time {
|
|
||||||
if !sv.testNow.IsZero() {
|
|
||||||
return sv.testNow
|
|
||||||
}
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv signVerifier) signRaw(
|
|
||||||
r io.Reader,
|
|
||||||
sigLen uint8, salt []byte, t time.Time,
|
|
||||||
) (
|
|
||||||
[]byte, error,
|
|
||||||
) {
|
|
||||||
h := hmac.New(sha256.New, sv.secret)
|
|
||||||
r = sigPrefixReader(r, sigLen, salt, t)
|
|
||||||
if _, err := io.Copy(h, r); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return h.Sum(nil)[:sigLen], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv signVerifier) sign(r io.Reader) (Signature, error) {
|
|
||||||
salt := make([]byte, 8)
|
|
||||||
if _, err := rand.Read(salt); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t := sv.now()
|
|
||||||
sig, err := sv.signRaw(r, sv.outSize, salt, t)
|
|
||||||
return Signature{sig: sig, salt: salt, t: t}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv signVerifier) verify(s Signature, r io.Reader) error {
|
|
||||||
sig, err := sv.signRaw(r, uint8(len(s.sig)), s.salt, s.t)
|
|
||||||
if err != nil {
|
|
||||||
return mlog.ErrWithKV(err, s)
|
|
||||||
} else if !hmac.Equal(sig, s.sig) {
|
|
||||||
return mlog.ErrWithKV(ErrInvalidSig, s)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -10,58 +10,35 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestSignerVerifier(t *T) {
|
func TestSignerVerifier(t *T) {
|
||||||
secret := mtest.RandBytes(16)
|
secret := NewSecret(mtest.RandBytes(16))
|
||||||
sigI, ver := NewSignerVerifier(secret)
|
|
||||||
sig := sigI.(signVerifier)
|
|
||||||
weakSigI, weakVer := NewWeakSignerVerifier(secret)
|
|
||||||
weakSig := weakSigI.(signVerifier)
|
|
||||||
var prevStr string
|
var prevStr string
|
||||||
var prevSig, prevWeakSig Signature
|
var prevSig Signature
|
||||||
for i := 0; i < 10000; i++ {
|
for i := 0; i < 10000; i++ {
|
||||||
now := time.Now().Round(0)
|
now := time.Now().Round(0)
|
||||||
sig.testNow = now
|
secret.testNow = now
|
||||||
weakSig.testNow = now
|
|
||||||
|
|
||||||
thisStr := mtest.RandHex(512)
|
thisStr := mtest.RandHex(512)
|
||||||
thisSig := SignString(sig, thisStr)
|
thisSig := SignString(secret, thisStr)
|
||||||
thisWeakSig := SignString(weakSig, thisStr)
|
thisSigStr := thisSig.String()
|
||||||
thisSigStr, thisWeakSigStr := thisSig.String(), thisWeakSig.String()
|
|
||||||
|
|
||||||
// checking the times made it
|
|
||||||
assert.Equal(t, now, thisSig.Time())
|
|
||||||
assert.Equal(t, now, thisWeakSig.Time())
|
|
||||||
|
|
||||||
// sanity checks
|
// sanity checks
|
||||||
assert.NotEmpty(t, thisSigStr)
|
assert.NotEmpty(t, thisSigStr)
|
||||||
assert.NotEmpty(t, thisWeakSigStr)
|
assert.Equal(t, now, thisSig.Time())
|
||||||
assert.NotEqual(t, thisSigStr, thisWeakSigStr)
|
assert.NoError(t, VerifyString(secret, thisSig, thisStr))
|
||||||
assert.True(t, len(thisSigStr) > len(thisWeakSigStr))
|
|
||||||
|
|
||||||
// marshaling/unmarshaling
|
// marshaling/unmarshaling
|
||||||
var thisSig2, thisWeakSig2 Signature
|
var thisSig2 Signature
|
||||||
assert.NoError(t, thisSig2.UnmarshalText([]byte(thisSigStr)))
|
assert.NoError(t, thisSig2.UnmarshalText([]byte(thisSigStr)))
|
||||||
assert.Equal(t, thisSigStr, thisSig2.String())
|
assert.Equal(t, thisSigStr, thisSig2.String())
|
||||||
assert.NoError(t, thisWeakSig2.UnmarshalText([]byte(thisWeakSigStr)))
|
|
||||||
assert.Equal(t, thisWeakSigStr, thisWeakSig2.String())
|
|
||||||
assert.Equal(t, now, thisSig2.Time())
|
assert.Equal(t, now, thisSig2.Time())
|
||||||
assert.Equal(t, now, thisWeakSig2.Time())
|
assert.NoError(t, VerifyString(secret, thisSig2, thisStr))
|
||||||
|
|
||||||
// Either sigVer should be able to verify either signature
|
|
||||||
assert.NoError(t, VerifyString(ver, thisSig, thisStr))
|
|
||||||
assert.NoError(t, VerifyString(weakVer, thisWeakSig, thisStr))
|
|
||||||
assert.NoError(t, VerifyString(ver, thisWeakSig, thisStr))
|
|
||||||
assert.NoError(t, VerifyString(weakVer, thisSig, thisStr))
|
|
||||||
|
|
||||||
if prevStr != "" {
|
if prevStr != "" {
|
||||||
assert.NotEqual(t, prevSig.String(), thisSigStr)
|
assert.NotEqual(t, prevSig.String(), thisSigStr)
|
||||||
assert.NotEqual(t, prevWeakSig.String(), thisWeakSigStr)
|
err := VerifyString(secret, prevSig, thisStr)
|
||||||
err := VerifyString(ver, prevSig, thisStr)
|
|
||||||
assert.True(t, merry.Is(err, ErrInvalidSig))
|
|
||||||
err = VerifyString(ver, prevWeakSig, thisStr)
|
|
||||||
assert.True(t, merry.Is(err, ErrInvalidSig))
|
assert.True(t, merry.Is(err, ErrInvalidSig))
|
||||||
}
|
}
|
||||||
prevStr = thisStr
|
prevStr = thisStr
|
||||||
prevSig = thisSig
|
prevSig = thisSig
|
||||||
prevWeakSig = thisWeakSig
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user