forked from TrueCloudLab/frostfs-api-go
Merge pull request #96 from nspcc-dev/feature/matryoshka-signatures
service: sign requests on the principle of Matryoshka
This commit is contained in:
commit
4fadb5040b
4 changed files with 103 additions and 16 deletions
|
@ -137,11 +137,24 @@ func verifySignatures(src SignedDataSource, items ...SignKeyPair) error {
|
||||||
}
|
}
|
||||||
defer bytesPool.Put(data)
|
defer bytesPool.Put(data)
|
||||||
|
|
||||||
for _, signKey := range items {
|
for i := range items {
|
||||||
|
if i > 0 {
|
||||||
|
// add previous key bytes to the signed message
|
||||||
|
|
||||||
|
signKeyDataSrc := SignKeyPairsSignedData(items[i-1])
|
||||||
|
|
||||||
|
signKeyData, err := signKeyDataSrc.SignedData()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not get signed data of key-signature #%d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
data = append(data, signKeyData...)
|
||||||
|
}
|
||||||
|
|
||||||
if err := crypto.Verify(
|
if err := crypto.Verify(
|
||||||
signKey.GetPublicKey(),
|
items[i].GetPublicKey(),
|
||||||
data,
|
data,
|
||||||
signKey.GetSignature(),
|
items[i].GetSignature(),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -213,6 +226,7 @@ func SignRequestData(key *ecdsa.PrivateKey, src RequestSignedData) error {
|
||||||
src.GetBearerToken(),
|
src.GetBearerToken(),
|
||||||
),
|
),
|
||||||
ExtendedHeadersSignedData(src),
|
ExtendedHeadersSignedData(src),
|
||||||
|
SignKeyPairsSignedData(src.GetSignKeyPairs()...),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -15,13 +15,13 @@ import (
|
||||||
type testSignedDataSrc struct {
|
type testSignedDataSrc struct {
|
||||||
err error
|
err error
|
||||||
data []byte
|
data []byte
|
||||||
sig []byte
|
|
||||||
key *ecdsa.PublicKey
|
|
||||||
token SessionToken
|
token SessionToken
|
||||||
|
|
||||||
bearer BearerToken
|
bearer BearerToken
|
||||||
|
|
||||||
extHdrs []ExtendedHeader
|
extHdrs []ExtendedHeader
|
||||||
|
|
||||||
|
signKeys []SignKeyPair
|
||||||
}
|
}
|
||||||
|
|
||||||
type testSignedDataReader struct {
|
type testSignedDataReader struct {
|
||||||
|
@ -29,13 +29,15 @@ type testSignedDataReader struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s testSignedDataSrc) GetSignature() []byte {
|
func (s testSignedDataSrc) GetSignature() []byte {
|
||||||
return s.sig
|
if len(s.signKeys) > 0 {
|
||||||
|
return s.signKeys[0].GetSignature()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s testSignedDataSrc) GetSignKeyPairs() []SignKeyPair {
|
func (s testSignedDataSrc) GetSignKeyPairs() []SignKeyPair {
|
||||||
return []SignKeyPair{
|
return s.signKeys
|
||||||
newSignatureKeyPair(s.key, s.sig),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s testSignedDataSrc) SignedData() ([]byte, error) {
|
func (s testSignedDataSrc) SignedData() ([]byte, error) {
|
||||||
|
@ -43,8 +45,9 @@ func (s testSignedDataSrc) SignedData() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *testSignedDataSrc) AddSignKey(sig []byte, key *ecdsa.PublicKey) {
|
func (s *testSignedDataSrc) AddSignKey(sig []byte, key *ecdsa.PublicKey) {
|
||||||
s.key = key
|
s.signKeys = append(s.signKeys,
|
||||||
s.sig = sig
|
newSignatureKeyPair(key, sig),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testData(t *testing.T, sz int) []byte {
|
func testData(t *testing.T, sz int) []byte {
|
||||||
|
@ -209,23 +212,27 @@ func TestVerifyAccumulatedSignatures(t *testing.T) {
|
||||||
// create test private key
|
// create test private key
|
||||||
sk := test.DecodeKey(0)
|
sk := test.DecodeKey(0)
|
||||||
|
|
||||||
|
signKey := new(RequestVerificationHeader_Signature)
|
||||||
|
signKey.Peer = crypto.MarshalPublicKey(&sk.PublicKey)
|
||||||
|
|
||||||
// create signature source
|
// create signature source
|
||||||
src := &testSignedDataSrc{
|
src := &testSignedDataSrc{
|
||||||
data: testData(t, 10),
|
data: testData(t, 10),
|
||||||
key: &sk.PublicKey,
|
|
||||||
|
signKeys: []SignKeyPair{signKey},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// calculate a signature
|
// calculate a signature
|
||||||
src.sig, err = crypto.Sign(sk, src.data)
|
signKey.Sign, err = crypto.Sign(sk, src.data)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// ascertain that verification is passed
|
// ascertain that verification is passed
|
||||||
require.NoError(t, VerifyAccumulatedSignatures(src))
|
require.NoError(t, VerifyAccumulatedSignatures(src))
|
||||||
|
|
||||||
// break the signature
|
// break the signature
|
||||||
src.sig[0]++
|
signKey.Sign[0]++
|
||||||
|
|
||||||
// ascertain that verification is failed
|
// ascertain that verification is failed
|
||||||
require.Error(t, VerifyAccumulatedSignatures(src))
|
require.Error(t, VerifyAccumulatedSignatures(src))
|
||||||
|
@ -238,9 +245,13 @@ func TestVerifySignatureWithKey(t *testing.T) {
|
||||||
ErrEmptyDataWithSignature.Error(),
|
ErrEmptyDataWithSignature.Error(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
signKey := new(RequestVerificationHeader_Signature)
|
||||||
|
|
||||||
// create test signature source
|
// create test signature source
|
||||||
src := &testSignedDataSrc{
|
src := &testSignedDataSrc{
|
||||||
data: testData(t, 10),
|
data: testData(t, 10),
|
||||||
|
|
||||||
|
signKeys: []SignKeyPair{signKey},
|
||||||
}
|
}
|
||||||
|
|
||||||
// nil public key
|
// nil public key
|
||||||
|
@ -255,14 +266,14 @@ func TestVerifySignatureWithKey(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// calculate a signature
|
// calculate a signature
|
||||||
src.sig, err = crypto.Sign(sk, src.data)
|
signKey.Sign, err = crypto.Sign(sk, src.data)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// ascertain that verification is passed
|
// ascertain that verification is passed
|
||||||
require.NoError(t, VerifySignatureWithKey(&sk.PublicKey, src))
|
require.NoError(t, VerifySignatureWithKey(&sk.PublicKey, src))
|
||||||
|
|
||||||
// break the signature
|
// break the signature
|
||||||
src.sig[0]++
|
signKey.Sign[0]++
|
||||||
|
|
||||||
// ascertain that verification is failed
|
// ascertain that verification is failed
|
||||||
require.Error(t, VerifySignatureWithKey(&sk.PublicKey, src))
|
require.Error(t, VerifySignatureWithKey(&sk.PublicKey, src))
|
||||||
|
@ -375,4 +386,15 @@ func TestSignVerifyRequestData(t *testing.T) {
|
||||||
|
|
||||||
// ascertain that verification is passed
|
// ascertain that verification is passed
|
||||||
require.NoError(t, VerifyRequestData(rdr))
|
require.NoError(t, VerifyRequestData(rdr))
|
||||||
|
|
||||||
|
if len(rdr.GetSignKeyPairs()) < 2 {
|
||||||
|
// add one more signature
|
||||||
|
require.NoError(t, SignRequestData(test.DecodeKey(1), rdr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// change key-signature order
|
||||||
|
rdr.signKeys[0], rdr.signKeys[1] = rdr.signKeys[1], rdr.signKeys[0]
|
||||||
|
|
||||||
|
// ascertain that verification is failed
|
||||||
|
require.Error(t, VerifyRequestData(src))
|
||||||
}
|
}
|
||||||
|
|
|
@ -262,6 +262,7 @@ type RequestData interface {
|
||||||
type RequestSignedData interface {
|
type RequestSignedData interface {
|
||||||
RequestData
|
RequestData
|
||||||
SignKeyPairAccumulator
|
SignKeyPairAccumulator
|
||||||
|
SignKeyPairSource
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestVerifyData is an interface of request information with signature read access.
|
// RequestVerifyData is an interface of request information with signature read access.
|
||||||
|
|
|
@ -2,11 +2,16 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/internal"
|
"github.com/nspcc-dev/neofs-api-go/internal"
|
||||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
crypto "github.com/nspcc-dev/neofs-crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type signKeyPairsWrapper struct {
|
||||||
|
items []SignKeyPair
|
||||||
|
}
|
||||||
|
|
||||||
// GetSessionToken returns SessionToken interface of Token field.
|
// GetSessionToken returns SessionToken interface of Token field.
|
||||||
//
|
//
|
||||||
// If token field value is nil, nil returns.
|
// If token field value is nil, nil returns.
|
||||||
|
@ -114,3 +119,48 @@ func (m RequestVerificationHeader) GetBearerToken() BearerToken {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignKeyPairsSignedData wraps passed SignKeyPair slice and returns SignedDataSource interface.
|
||||||
|
func SignKeyPairsSignedData(v ...SignKeyPair) SignedDataSource {
|
||||||
|
return &signKeyPairsWrapper{
|
||||||
|
items: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedData returns signed SignKeyPair slice in a binary representation.
|
||||||
|
func (s signKeyPairsWrapper) SignedData() ([]byte, error) {
|
||||||
|
return SignedDataFromReader(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataSize returns the length of signed SignKeyPair slice.
|
||||||
|
func (s signKeyPairsWrapper) SignedDataSize() (sz int) {
|
||||||
|
for i := range s.items {
|
||||||
|
// add key length
|
||||||
|
sz += len(
|
||||||
|
crypto.MarshalPublicKey(s.items[i].GetPublicKey()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSignedData copies a binary representation of the signed SignKeyPair slice to passed buffer.
|
||||||
|
//
|
||||||
|
// If buffer length is less than required, io.ErrUnexpectedEOF returns.
|
||||||
|
func (s signKeyPairsWrapper) ReadSignedData(p []byte) (int, error) {
|
||||||
|
sz := s.SignedDataSize()
|
||||||
|
if len(p) < sz {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
off := 0
|
||||||
|
|
||||||
|
for i := range s.items {
|
||||||
|
// copy public key bytes
|
||||||
|
off += copy(p[off:], crypto.MarshalPublicKey(
|
||||||
|
s.items[i].GetPublicKey(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue