feat(eddsa): add support for EdDSA varsigs - and common Ed25519 and Ed448 algorithms
This commit is contained in:
16
common.go
16
common.go
@@ -1,5 +1,21 @@
|
||||
package varsig
|
||||
|
||||
// Ed25519 produces a varsig that describes the associated algorithm defined
|
||||
// by the [IANA JOSE specification].
|
||||
//
|
||||
// [IANA JOSE specidication]: https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms
|
||||
func Ed25519(payloadEncoding PayloadEncoding, opts ...Option) (*EdDSAVarsig, error) {
|
||||
return NewEdDSAVarsig(CurveEd25519, HashAlgorithmSHA512, payloadEncoding, opts...)
|
||||
}
|
||||
|
||||
// Ed448 produces a varsig that describes the associated algorithm defined
|
||||
// by the [IANA JOSE specification].
|
||||
//
|
||||
// [IANA JOSE specidication]: https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms
|
||||
func Ed448(payloadEncoding PayloadEncoding, opts ...Option) (*EdDSAVarsig, error) {
|
||||
return NewEdDSAVarsig(CurveEd448, HashAlgorithmShake256, payloadEncoding, opts...)
|
||||
}
|
||||
|
||||
// RS256 produces a varsig that describes the associated algorithm defined
|
||||
// by the [IANA JOSE specification].
|
||||
//
|
||||
|
||||
@@ -8,11 +8,27 @@ import (
|
||||
"github.com/selesy/go-varsig"
|
||||
)
|
||||
|
||||
func TestEd25519(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
in := mustVarsig[varsig.EdDSAVarsig](t)(varsig.Ed25519(varsig.PayloadEncodingDAGCBOR))
|
||||
out := roundTrip(t, in, "3401ed01ed011371")
|
||||
assertEdDSAEqual(t, in, out)
|
||||
}
|
||||
|
||||
func TestEd448(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
in := mustVarsig[varsig.EdDSAVarsig](t)(varsig.Ed448(varsig.PayloadEncodingDAGCBOR))
|
||||
out := roundTrip(t, in, "3401ed0183241971")
|
||||
assertEdDSAEqual(t, in, out)
|
||||
}
|
||||
|
||||
func TestRS256(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
in := mustVarsig[varsig.RSAVarsig](t)(varsig.RS256(0x100, varsig.PayloadEncodingDAGCBOR))
|
||||
out := roundTrip(t, in, "NAGFJBKAAnE")
|
||||
out := roundTrip(t, in, "3401852412800271")
|
||||
assertRSAEqual(t, in, out)
|
||||
}
|
||||
|
||||
@@ -20,7 +36,7 @@ func TestRS384(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
in := mustVarsig[varsig.RSAVarsig](t)(varsig.RS384(0x100, varsig.PayloadEncodingDAGCBOR))
|
||||
out := roundTrip(t, in, "NAGFJCCAAnE")
|
||||
out := roundTrip(t, in, "3401852420800271")
|
||||
assertRSAEqual(t, in, out)
|
||||
}
|
||||
|
||||
@@ -28,10 +44,17 @@ func TestRS512(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
in := mustVarsig[varsig.RSAVarsig](t)(varsig.RS512(0x100, varsig.PayloadEncodingDAGCBOR))
|
||||
out := roundTrip(t, in, "NAGFJBOAAnE")
|
||||
out := roundTrip(t, in, "3401852413800271")
|
||||
assertRSAEqual(t, in, out)
|
||||
}
|
||||
|
||||
func assertEdDSAEqual(t *testing.T, in, out *varsig.EdDSAVarsig) {
|
||||
t.Helper()
|
||||
|
||||
assert.Equal(t, in.Curve(), out.Curve())
|
||||
assert.Equal(t, in.HashAlgorithm(), out.HashAlgorithm())
|
||||
}
|
||||
|
||||
func assertRSAEqual(t *testing.T, in, out *varsig.RSAVarsig) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
10
constant.go
10
constant.go
@@ -14,9 +14,10 @@ type HashAlgorithm uint64
|
||||
|
||||
const (
|
||||
HashAlgorithmUnspecified HashAlgorithm = 0x00
|
||||
HashAlgorithmSHA256 HashAlgorithm = HashAlgorithm(multicodec.Sha2_256)
|
||||
HashAlgorithmSHA384 HashAlgorithm = HashAlgorithm(multicodec.Sha2_384)
|
||||
HashAlgorithmSHA512 HashAlgorithm = HashAlgorithm(multicodec.Sha2_512)
|
||||
HashAlgorithmSHA256 = HashAlgorithm(multicodec.Sha2_256)
|
||||
HashAlgorithmSHA384 = HashAlgorithm(multicodec.Sha2_384)
|
||||
HashAlgorithmSHA512 = HashAlgorithm(multicodec.Sha2_512)
|
||||
HashAlgorithmShake256 = HashAlgorithm(multicodec.Shake256)
|
||||
)
|
||||
|
||||
// DecodeHashAlgorithm reads and validates the expected hash algorithm
|
||||
@@ -34,8 +35,9 @@ func DecodeHashAlgorithm(r *bytes.Reader) (HashAlgorithm, error) {
|
||||
HashAlgorithmSHA256: {},
|
||||
HashAlgorithmSHA384: {},
|
||||
HashAlgorithmSHA512: {},
|
||||
HashAlgorithmShake256: {},
|
||||
}[h]; !ok {
|
||||
return HashAlgorithmUnspecified, ErrUnknownHashAlgorithm
|
||||
return HashAlgorithmUnspecified, fmt.Errorf("%w: %x", ErrUnknownHashAlgorithm, h)
|
||||
}
|
||||
|
||||
return h, nil
|
||||
|
||||
116
eddsa.go
Normal file
116
eddsa.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package varsig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/multiformats/go-multicodec"
|
||||
)
|
||||
|
||||
const (
|
||||
SignAlgorithmEdDSA = SignAlgorithm(multicodec.Ed25519Pub)
|
||||
SignAlgorithmEd25519 = SignAlgorithm(multicodec.Ed25519Pub)
|
||||
SignAlgorithmEd448 = SignAlgorithm(multicodec.Ed448Pub)
|
||||
)
|
||||
|
||||
type EdDSACurve uint64
|
||||
|
||||
const (
|
||||
CurveEd25519 = EdDSACurve(multicodec.Ed25519Pub)
|
||||
CurveEd448 = EdDSACurve(multicodec.Ed448Pub)
|
||||
)
|
||||
|
||||
var _ Varsig = (*EdDSAVarsig)(nil)
|
||||
|
||||
type EdDSAVarsig struct {
|
||||
varsig[EdDSAVarsig]
|
||||
|
||||
curve EdDSACurve
|
||||
hashAlg HashAlgorithm
|
||||
}
|
||||
|
||||
func NewEdDSAVarsig(curve EdDSACurve, hashAlgorithm HashAlgorithm, payloadEncoding PayloadEncoding, opts ...Option) (*EdDSAVarsig, error) {
|
||||
options := newOptions(opts...)
|
||||
|
||||
var (
|
||||
vers = Version1
|
||||
signAlg = SignAlgorithmEdDSA
|
||||
sig = []byte{}
|
||||
)
|
||||
|
||||
if options.ForceVersion0() {
|
||||
vers = Version0
|
||||
signAlg = SignAlgorithm(curve)
|
||||
sig = options.Signature()
|
||||
}
|
||||
|
||||
v := &EdDSAVarsig{
|
||||
varsig: varsig[EdDSAVarsig]{
|
||||
vers: vers,
|
||||
signAlg: signAlg,
|
||||
payEnc: payloadEncoding,
|
||||
sig: sig,
|
||||
},
|
||||
curve: curve,
|
||||
hashAlg: hashAlgorithm,
|
||||
}
|
||||
|
||||
return v.validateSig(v, ed25519.PrivateKeySize)
|
||||
}
|
||||
|
||||
func (v *EdDSAVarsig) Curve() EdDSACurve {
|
||||
return v.curve
|
||||
}
|
||||
|
||||
func (v *EdDSAVarsig) HashAlgorithm() HashAlgorithm {
|
||||
return v.hashAlg
|
||||
}
|
||||
|
||||
func (v EdDSAVarsig) Encode() []byte {
|
||||
buf := v.encode()
|
||||
|
||||
if v.vers != Version0 {
|
||||
buf = binary.AppendUvarint(buf, uint64(v.curve))
|
||||
}
|
||||
|
||||
buf = binary.AppendUvarint(buf, uint64(v.hashAlg))
|
||||
buf = binary.AppendUvarint(buf, uint64(v.payEnc))
|
||||
buf = append(buf, v.Signature()...)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func decodeEd25519(r *bytes.Reader, vers Version, signAlg SignAlgorithm) (Varsig, error) {
|
||||
curve := uint64(signAlg)
|
||||
if vers != Version0 {
|
||||
u, err := binary.ReadUvarint(r)
|
||||
|
||||
if err != nil {
|
||||
return nil, err // TODO: wrap error?
|
||||
}
|
||||
|
||||
curve = u
|
||||
}
|
||||
|
||||
hashAlg, err := binary.ReadUvarint(r)
|
||||
if err != nil {
|
||||
return nil, err // TODO: wrap error?
|
||||
}
|
||||
|
||||
v := &EdDSAVarsig{
|
||||
varsig: varsig[EdDSAVarsig]{
|
||||
vers: vers,
|
||||
signAlg: signAlg,
|
||||
},
|
||||
curve: EdDSACurve(curve),
|
||||
hashAlg: HashAlgorithm(hashAlg),
|
||||
}
|
||||
|
||||
return v.decodePayEncAndSig(r, v, ed25519.PrivateKeySize)
|
||||
}
|
||||
|
||||
// TODO: remove this when parseEd25519 is added to the DefaultRegistry.
|
||||
func Junk() {
|
||||
_, _ = decodeEd25519(nil, 0, 0)
|
||||
}
|
||||
58
eddsa_test.go
Normal file
58
eddsa_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package varsig_test
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/selesy/go-varsig"
|
||||
)
|
||||
|
||||
func TestDecodeEd25519(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("passes - section 3 example - v0", func(t *testing.T) {
|
||||
// Original: 34ed01 1371ae3784f03f9ee1163382fa6efa73b0c31ecf58c899c836709303ba4621d1e6df20e09aaa568914290b7ea124f5b38e70b9b69c7de0d216880eac885edd41c302
|
||||
// Corrected: 34ed011371ae3784f03f9ee1163382fa6efa73b0c31ecf58c899c836709303ba4621d1e6df20e09aaa568914290b7ea124f5b38e70b9b69c7de0d216880eac885edd41c302")
|
||||
|
||||
hdr, err := hex.DecodeString("34ed011371")
|
||||
require.NoError(t, err)
|
||||
|
||||
sig, err := hex.DecodeString("ae3784f03f9ee1163382fa6efa73b0c31ecf58c899c836709303ba4621d1e6df20e09aaa568914290b7ea124f5b38e70b9b69c7de0d216880eac885edd41c302")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sig, 64)
|
||||
|
||||
t.Run("Decode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
v, err := varsig.Decode(append(hdr, sig...))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, v)
|
||||
assert.Equal(t, varsig.Version0, v.Version())
|
||||
assert.Equal(t, varsig.SignAlgorithmEd25519, v.SignatureAlgorithm())
|
||||
assert.Equal(t, varsig.PayloadEncodingDAGCBOR, v.PayloadEncoding())
|
||||
assert.Len(t, v.Signature(), 64)
|
||||
|
||||
impl, ok := v.(*varsig.EdDSAVarsig)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, varsig.CurveEd25519, impl.Curve())
|
||||
assert.Equal(t, varsig.HashAlgorithmSHA512, impl.HashAlgorithm())
|
||||
})
|
||||
|
||||
t.Run("Encode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
v, err := varsig.NewEdDSAVarsig(
|
||||
varsig.CurveEd25519,
|
||||
varsig.HashAlgorithmSHA512,
|
||||
varsig.PayloadEncodingDAGCBOR,
|
||||
varsig.WithForceVersion0(sig),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, v)
|
||||
assert.Equal(t, append(hdr, sig...), v.Encode())
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -26,7 +26,8 @@ type Registry map[SignAlgorithm]DecodeFunc
|
||||
func DefaultRegistry() Registry {
|
||||
return map[SignAlgorithm]DecodeFunc{
|
||||
SignAlgorithmRSA: decodeRSA,
|
||||
SignAlgorithmEd25519: notYetImplementedVarsigDecoder,
|
||||
SignAlgorithmEdDSA: decodeEd25519,
|
||||
SignAlgorithmEd448: decodeEd25519,
|
||||
SignAlgorithmECDSAP256: notYetImplementedVarsigDecoder,
|
||||
SignAlgorithmECDSASecp256k1: notYetImplementedVarsigDecoder,
|
||||
SignAlgorithmECDSAP521: notYetImplementedVarsigDecoder,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package varsig_test
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
@@ -173,7 +172,7 @@ func mustVarsig[T varsig.Varsig](t *testing.T) func(*T, error) *T {
|
||||
|
||||
func roundTrip[T varsig.Varsig](t *testing.T, in T, expEncHex string) T {
|
||||
data := in.Encode()
|
||||
assert.Equal(t, expEncHex, base64.RawStdEncoding.EncodeToString(data))
|
||||
assert.Equal(t, expEncHex, hex.EncodeToString(data))
|
||||
|
||||
out, err := varsig.Decode(in.Encode())
|
||||
if err != nil && (out.Version() != varsig.Version0 || !errors.Is(err, varsig.ErrMissingSignature)) {
|
||||
|
||||
Reference in New Issue
Block a user