forked from TrueCloudLab/frostfs-api-go
Merge pull request #89 from nspcc-dev/feature/bearer-token-definition
service: define BearerToken interface
This commit is contained in:
commit
73e90be443
3 changed files with 302 additions and 0 deletions
96
service/bearer.go
Normal file
96
service/bearer.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/refs"
|
||||||
|
crypto "github.com/nspcc-dev/neofs-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type signedBearerToken struct {
|
||||||
|
BearerToken
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixedBearerTokenDataSize = 0 +
|
||||||
|
refs.OwnerIDSize +
|
||||||
|
8
|
||||||
|
|
||||||
|
// NewSignedBearerToken wraps passed BearerToken in a component suitable for signing.
|
||||||
|
//
|
||||||
|
// Result can be used in AddSignatureWithKey function.
|
||||||
|
func NewSignedBearerToken(token BearerToken) DataWithSignKeyAccumulator {
|
||||||
|
return &signedBearerToken{
|
||||||
|
BearerToken: token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVerifiedBearerToken wraps passed SessionToken in a component suitable for signature verification.
|
||||||
|
//
|
||||||
|
// Result can be used in VerifySignatureWithKey function.
|
||||||
|
func NewVerifiedBearerToken(token BearerToken) DataWithSignature {
|
||||||
|
return &signedBearerToken{
|
||||||
|
BearerToken: token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSignKey calls a Signature field setter and an OwnerKey field setter with corresponding arguments.
|
||||||
|
func (s signedBearerToken) AddSignKey(sig []byte, key *ecdsa.PublicKey) {
|
||||||
|
if s.BearerToken != nil {
|
||||||
|
s.SetSignature(sig)
|
||||||
|
|
||||||
|
s.SetOwnerKey(
|
||||||
|
crypto.MarshalPublicKey(key),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedData returns token information in a binary representation.
|
||||||
|
func (s signedBearerToken) SignedData() ([]byte, error) {
|
||||||
|
return SignedDataFromReader(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataSize returns the length of signed token information slice.
|
||||||
|
func (s signedBearerToken) SignedDataSize() int {
|
||||||
|
return bearerTokenInfoSize(s.BearerToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSignedData copies a binary representation of the token information to passed buffer.
|
||||||
|
//
|
||||||
|
// If buffer length is less than required, io.ErrUnexpectedEOF returns.
|
||||||
|
func (s signedBearerToken) ReadSignedData(p []byte) (int, error) {
|
||||||
|
sz := s.SignedDataSize()
|
||||||
|
if len(p) < sz {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
copyBearerTokenSignedData(p, s.BearerToken)
|
||||||
|
|
||||||
|
return sz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bearerTokenInfoSize(v ACLRulesSource) int {
|
||||||
|
if v == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return fixedBearerTokenDataSize + len(v.GetACLRules())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fills passed buffer with signing token information bytes.
|
||||||
|
// Does not check buffer length, it is understood that enough space is allocated in it.
|
||||||
|
//
|
||||||
|
// If passed BearerTokenInfo, buffer remains unchanged.
|
||||||
|
func copyBearerTokenSignedData(buf []byte, token BearerTokenInfo) {
|
||||||
|
if token == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var off int
|
||||||
|
|
||||||
|
off += copy(buf[off:], token.GetACLRules())
|
||||||
|
|
||||||
|
off += copy(buf[off:], token.GetOwnerID().Bytes())
|
||||||
|
|
||||||
|
tokenEndianness.PutUint64(buf[off:], token.ExpirationEpoch())
|
||||||
|
off += 8
|
||||||
|
}
|
172
service/bearer_test.go
Normal file
172
service/bearer_test.go
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-crypto/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testBearerToken struct {
|
||||||
|
aclRules []byte
|
||||||
|
expEpoch uint64
|
||||||
|
owner OwnerID
|
||||||
|
key []byte
|
||||||
|
sig []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s testBearerToken) GetACLRules() []byte {
|
||||||
|
return s.aclRules
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testBearerToken) SetACLRules(v []byte) {
|
||||||
|
s.aclRules = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s testBearerToken) ExpirationEpoch() uint64 {
|
||||||
|
return s.expEpoch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testBearerToken) SetExpirationEpoch(v uint64) {
|
||||||
|
s.expEpoch = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s testBearerToken) GetOwnerID() OwnerID {
|
||||||
|
return s.owner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testBearerToken) SetOwnerID(v OwnerID) {
|
||||||
|
s.owner = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s testBearerToken) GetOwnerKey() []byte {
|
||||||
|
return s.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testBearerToken) SetOwnerKey(v []byte) {
|
||||||
|
s.key = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s testBearerToken) GetSignature() []byte {
|
||||||
|
return s.sig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testBearerToken) SetSignature(v []byte) {
|
||||||
|
s.sig = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBearerTokenMsgGettersSetters(t *testing.T) {
|
||||||
|
var tok BearerToken = new(testBearerToken)
|
||||||
|
|
||||||
|
{ // ACLRules
|
||||||
|
rules := []byte{1, 2, 3}
|
||||||
|
|
||||||
|
tok.SetACLRules(rules)
|
||||||
|
|
||||||
|
require.Equal(t, rules, tok.GetACLRules())
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // OwnerID
|
||||||
|
ownerID := OwnerID{}
|
||||||
|
_, err := rand.Read(ownerID[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tok.SetOwnerID(ownerID)
|
||||||
|
|
||||||
|
require.Equal(t, ownerID, tok.GetOwnerID())
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // ValidUntil
|
||||||
|
e := uint64(5)
|
||||||
|
|
||||||
|
tok.SetExpirationEpoch(e)
|
||||||
|
|
||||||
|
require.Equal(t, e, tok.ExpirationEpoch())
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // OwnerKey
|
||||||
|
key := make([]byte, 10)
|
||||||
|
_, err := rand.Read(key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tok.SetOwnerKey(key)
|
||||||
|
|
||||||
|
require.Equal(t, key, tok.GetOwnerKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Signature
|
||||||
|
sig := make([]byte, 10)
|
||||||
|
_, err := rand.Read(sig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tok.SetSignature(sig)
|
||||||
|
|
||||||
|
require.Equal(t, sig, tok.GetSignature())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignVerifyBearerToken(t *testing.T) {
|
||||||
|
var token BearerToken = new(testBearerToken)
|
||||||
|
|
||||||
|
// create private key for signing
|
||||||
|
sk := test.DecodeKey(0)
|
||||||
|
pk := &sk.PublicKey
|
||||||
|
|
||||||
|
rules := []byte{1, 2, 3}
|
||||||
|
token.SetACLRules(rules)
|
||||||
|
|
||||||
|
ownerID := OwnerID{}
|
||||||
|
_, err := rand.Read(ownerID[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
token.SetOwnerID(ownerID)
|
||||||
|
|
||||||
|
fEpoch := uint64(2)
|
||||||
|
token.SetExpirationEpoch(fEpoch)
|
||||||
|
|
||||||
|
signedToken := NewSignedBearerToken(token)
|
||||||
|
verifiedToken := NewVerifiedBearerToken(token)
|
||||||
|
|
||||||
|
// sign and verify token
|
||||||
|
require.NoError(t, AddSignatureWithKey(sk, signedToken))
|
||||||
|
require.NoError(t, VerifySignatureWithKey(pk, verifiedToken))
|
||||||
|
|
||||||
|
items := []struct {
|
||||||
|
corrupt func()
|
||||||
|
restore func()
|
||||||
|
}{
|
||||||
|
{ // ACLRules
|
||||||
|
corrupt: func() {
|
||||||
|
token.SetACLRules(append(rules, 1))
|
||||||
|
},
|
||||||
|
restore: func() {
|
||||||
|
token.SetACLRules(rules)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // Owner ID
|
||||||
|
corrupt: func() {
|
||||||
|
ownerID[0]++
|
||||||
|
token.SetOwnerID(ownerID)
|
||||||
|
},
|
||||||
|
restore: func() {
|
||||||
|
ownerID[0]--
|
||||||
|
token.SetOwnerID(ownerID)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // Expiration epoch
|
||||||
|
corrupt: func() {
|
||||||
|
token.SetExpirationEpoch(fEpoch + 1)
|
||||||
|
},
|
||||||
|
restore: func() {
|
||||||
|
token.SetExpirationEpoch(fEpoch)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range items {
|
||||||
|
v.corrupt()
|
||||||
|
require.Error(t, VerifySignatureWithKey(pk, verifiedToken))
|
||||||
|
v.restore()
|
||||||
|
require.NoError(t, VerifySignatureWithKey(pk, verifiedToken))
|
||||||
|
}
|
||||||
|
}
|
|
@ -267,3 +267,37 @@ type RequestVerifyData interface {
|
||||||
RequestData
|
RequestData
|
||||||
SignKeyPairSource
|
SignKeyPairSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ACLRulesSource is an interface of the container of binary extended ACL rules with read access.
|
||||||
|
type ACLRulesSource interface {
|
||||||
|
GetACLRules() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACLRulesContainer is an interface of the container of binary extended ACL rules.
|
||||||
|
type ACLRulesContainer interface {
|
||||||
|
ACLRulesSource
|
||||||
|
SetACLRules([]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BearerTokenInfo is an interface of a fixed set of Bearer token information value containers.
|
||||||
|
// Contains:
|
||||||
|
// - binary extended ACL rules;
|
||||||
|
// - expiration epoch number;
|
||||||
|
// - ID of the token's owner.
|
||||||
|
type BearerTokenInfo interface {
|
||||||
|
ACLRulesContainer
|
||||||
|
ExpirationEpochContainer
|
||||||
|
OwnerIDContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
// BearerToken is an interface of Bearer token information and key-signature pair.
|
||||||
|
type BearerToken interface {
|
||||||
|
BearerTokenInfo
|
||||||
|
OwnerKeyContainer
|
||||||
|
SignatureContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
// BearerTokenSource is an interface of the container of a BearerToken with read access.
|
||||||
|
type BearerTokenSource interface {
|
||||||
|
GetBearerToken() BearerToken
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue