github.com/code-to-go/safepool.lib@v0.0.0-20221205180519-ee25e63c226e/pool/access.go (about)

     1  package pool
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/aes"
     6  	"hash"
     7  	"path"
     8  	"time"
     9  
    10  	"github.com/code-to-go/safepool.lib/core"
    11  	"github.com/code-to-go/safepool.lib/security"
    12  	"github.com/code-to-go/safepool.lib/transport"
    13  
    14  	"github.com/godruoyi/go-snowflake"
    15  )
    16  
    17  type Grant struct {
    18  	Identity    security.Identity
    19  	Since       uint64
    20  	KeystoreKey []byte
    21  }
    22  
    23  type AccessFile struct {
    24  	Version     float32
    25  	Grants      []Grant
    26  	Nonce       []byte
    27  	MasterKeyId uint64
    28  	Keystore    []byte
    29  }
    30  
    31  func (p *Pool) ImportAccess(e transport.Exchanger) (hash.Hash, error) {
    32  	l, err := p.lockAccessFile(e)
    33  	if core.IsErr(err, "cannot lock access on %s: %v", p.e) {
    34  		return nil, err
    35  	}
    36  	defer p.unlockAccessFile(e, l)
    37  
    38  	a, h, err := p.readAccessFile(e)
    39  	if core.IsErr(err, "cannot read access file:%v") {
    40  		return nil, err
    41  	}
    42  	if bytes.Equal(h.Sum(nil), p.accessHash) {
    43  		return h, nil
    44  	}
    45  
    46  	if p.masterKeyId != a.MasterKeyId {
    47  		err = p.importMasterKey(a)
    48  		if core.IsErr(err, "cannot import master key: %v") {
    49  			return nil, err
    50  		}
    51  	}
    52  
    53  	ks, err := p.importKeystore(a)
    54  	if core.IsErr(err, "cannot sync grants: %v") {
    55  		return nil, err
    56  	}
    57  
    58  	err = p.importGrants(a, ks)
    59  	if core.IsErr(err, "cannot sync grants: %v") {
    60  		return nil, err
    61  	}
    62  
    63  	p.accessHash = h.Sum(nil)
    64  	return h, nil
    65  }
    66  
    67  func (p *Pool) ExportAccessFile(e transport.Exchanger) error {
    68  	identities, err := p.sqlGetIdentities(false)
    69  	if core.IsErr(err, "cannot get identities for pool '%s':%v", p.Name) {
    70  		return err
    71  	}
    72  
    73  	var grants []Grant
    74  	for _, i := range identities {
    75  		keystoreKey, err := security.EcEncrypt(i.Identity, p.masterKey)
    76  		if core.IsErr(err, "cannot encrypt master key for identity %s: %v", i.Nick) {
    77  			return err
    78  		}
    79  		grants = append(grants, Grant{
    80  			Identity:    i.Public(),
    81  			Since:       p.masterKeyId,
    82  			KeystoreKey: keystoreKey,
    83  		})
    84  	}
    85  
    86  	ks, err := p.sqlGetKeystore()
    87  	if core.IsErr(err, "cannot read keystore from db for pool '%s': %v", p.Name) {
    88  		return err
    89  	}
    90  
    91  	noonce := security.GenerateBytesKey(aes.BlockSize)
    92  	cipherks, err := p.marshalKeystore(p.masterKey, noonce, ks)
    93  	if core.IsErr(err, "cannot marshal keystore for pool '%s': %v", p.Name) {
    94  		return err
    95  	}
    96  
    97  	a := AccessFile{
    98  		Version:     0.0,
    99  		Nonce:       noonce,
   100  		MasterKeyId: p.masterKeyId,
   101  		Grants:      grants,
   102  		Keystore:    cipherks,
   103  	}
   104  
   105  	l, err := p.lockAccessFile(p.e)
   106  	if core.IsErr(err, "cannot lock access on %s: %v", p.e) {
   107  		return err
   108  	}
   109  	defer p.unlockAccessFile(e, l)
   110  	_, err = p.writeAccessFile(e, a)
   111  	return err
   112  }
   113  
   114  func (p *Pool) importMasterKey(a AccessFile) error {
   115  	for _, g := range a.Grants {
   116  		if security.SameIdentity(p.Self, g.Identity) {
   117  			masterKey, err := security.EcDecrypt(p.Self, g.KeystoreKey)
   118  			if !core.IsErr(err, "corrupted master key in access grant: %v", err) {
   119  				err = p.sqlSetKey(a.MasterKeyId, masterKey)
   120  				if core.IsErr(err, "cannot write master key to db: %v", err) {
   121  					return err
   122  				}
   123  			}
   124  		}
   125  	}
   126  	p.masterKeyId = a.MasterKeyId
   127  	return nil
   128  }
   129  
   130  func (p *Pool) importGrants(a AccessFile, ks Keystore) error {
   131  	identities, err := p.sqlGetIdentities(false)
   132  	if core.IsErr(err, "cannot read identities during grant import: %v", err) {
   133  		return err
   134  	}
   135  	is := map[string]Identity{}
   136  	for _, i := range identities {
   137  		k := string(append(i.SignatureKey.Public, i.EncryptionKey.Public...))
   138  		is[k] = i
   139  	}
   140  
   141  	for _, g := range a.Grants {
   142  		i := g.Identity
   143  		k := string(append(i.SignatureKey.Public, i.EncryptionKey.Public...))
   144  		if _, found := is[k]; !found {
   145  			err := security.SetIdentity(i)
   146  			if !core.IsErr(err, "cannot add identity %s: %v", g.Identity.Nick) {
   147  				p.sqlSetIdentity(i, g.Since)
   148  			}
   149  			delete(is, k)
   150  		}
   151  	}
   152  
   153  	needNewMasterKey := false
   154  	for _, i := range is {
   155  		if _, ok := ks[i.Since]; ok {
   156  			p.sqlDeleteIdentity(i)
   157  			needNewMasterKey = true
   158  		}
   159  	}
   160  	if needNewMasterKey {
   161  		p.masterKeyId = snowflake.ID()
   162  		p.masterKey = security.GenerateBytesKey(32)
   163  		err = p.sqlSetKey(p.masterKeyId, p.masterKey)
   164  		if core.IsErr(err, "çannot store master encryption key to db: %v") {
   165  			return err
   166  		}
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  func (p *Pool) lockAccessFile(e transport.Exchanger) (uint64, error) {
   173  	lockFile := path.Join(p.Name, ".access.lock")
   174  	lockId, err := transport.LockFile(e, lockFile, time.Minute)
   175  	core.IsErr(err, "cannot lock access on %s: %v", p.Name, err)
   176  	return lockId, err
   177  }
   178  
   179  func (p *Pool) unlockAccessFile(e transport.Exchanger, lockId uint64) {
   180  	lockFile := path.Join(p.Name, ".access.lock")
   181  	transport.UnlockFile(e, lockFile, lockId)
   182  }
   183  
   184  func (p *Pool) readAccessFile(e transport.Exchanger) (AccessFile, hash.Hash, error) {
   185  	var a AccessFile
   186  	var sh security.SignedHash
   187  	signatureFile := path.Join(p.Name, ".access.sign")
   188  	accessFile := path.Join(p.Name, ".access")
   189  
   190  	err := transport.ReadJSON(e, signatureFile, &sh, nil)
   191  	if core.IsErr(err, "cannot read signature file '%s': %v", signatureFile, err) {
   192  		return AccessFile{}, nil, err
   193  	}
   194  
   195  	h := security.NewHash()
   196  	err = transport.ReadJSON(e, accessFile, &a, h)
   197  	if core.IsErr(err, "cannot read access file: %s", err) {
   198  		return AccessFile{}, nil, err
   199  	}
   200  
   201  	if security.VerifySignedHash(sh, []security.Identity{p.Self}, h.Sum(nil)) {
   202  		return a, h, nil
   203  	}
   204  
   205  	trusted, err := p.sqlGetIdentities(true)
   206  	if core.IsErr(err, "cannot get trusted identities: %v") {
   207  		return AccessFile{}, nil, nil
   208  	}
   209  
   210  	var is []security.Identity
   211  	for _, t := range trusted {
   212  		is = append(is, t.Identity)
   213  	}
   214  
   215  	if !security.VerifySignedHash(sh, is, h.Sum(nil)) {
   216  		return AccessFile{}, nil, ErrNotTrusted
   217  	}
   218  
   219  	_ = security.AppendToSignedHash(sh, p.Self)
   220  	if !core.IsErr(err, "cannot lock access on %s: %v", p.Name, err) {
   221  		if security.AppendToSignedHash(sh, p.Self) == nil {
   222  			err = transport.WriteJSON(e, signatureFile, sh, nil)
   223  			core.IsErr(err, "cannot write signature file on %s: %v", p.Name, err)
   224  		}
   225  	}
   226  
   227  	return a, h, nil
   228  }
   229  
   230  func (p *Pool) writeAccessFile(e transport.Exchanger, a AccessFile) (hash.Hash, error) {
   231  	lockFile := path.Join(p.Name, ".access.lock")
   232  	signatureFile := path.Join(p.Name, ".access.sign")
   233  	accessFile := path.Join(p.Name, ".access")
   234  
   235  	lockId, err := transport.LockFile(e, lockFile, time.Minute)
   236  	if core.IsErr(err, "cannot lock access on %s: %v", p.Name, err) {
   237  		return nil, err
   238  	}
   239  	defer transport.UnlockFile(e, lockFile, lockId)
   240  
   241  	h := security.NewHash()
   242  	err = transport.WriteJSON(e, accessFile, a, h)
   243  	if core.IsErr(err, "cannot write access file on %s: %v", p.Name, err) {
   244  		return nil, err
   245  	}
   246  
   247  	sh, err := security.NewSignedHash(h.Sum(nil), p.Self)
   248  	if core.IsErr(err, "cannot generate signature hash on %s: %v", p.Name, err) {
   249  		return nil, err
   250  	}
   251  	err = transport.WriteJSON(e, signatureFile, sh, nil)
   252  	if core.IsErr(err, "cannot write signature file on %s: %v", p.Name, err) {
   253  		return nil, err
   254  	}
   255  
   256  	return h, nil
   257  }