File: drive.go

package info (click to toggle)
golang-github-henrybear327-proton-api-bridge 1.0.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 668 kB
  • sloc: makefile: 3
file content (207 lines) | stat: -rw-r--r-- 5,819 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package proton_api_bridge

import (
	"context"
	"log"

	"github.com/henrybear327/Proton-API-Bridge/common"
	"golang.org/x/sync/semaphore"

	"github.com/ProtonMail/gopenpgp/v2/crypto"
	"github.com/henrybear327/go-proton-api"
)

type ProtonDrive struct {
	MainShare *proton.Share
	RootLink  *proton.Link

	MainShareKR   *crypto.KeyRing
	DefaultAddrKR *crypto.KeyRing

	Config *common.Config

	c                *proton.Client
	m                *proton.Manager
	userKR           *crypto.KeyRing
	addrKRs          map[string]*crypto.KeyRing
	addrData         map[string]proton.Address
	signatureAddress string

	cache                *cache
	blockUploadSemaphore *semaphore.Weighted
	blockCryptoSemaphore *semaphore.Weighted
}

func NewDefaultConfig() *common.Config {
	return common.NewConfigWithDefaultValues()
}

func NewProtonDrive(ctx context.Context, config *common.Config, authHandler proton.AuthHandler, deAuthHandler proton.Handler) (*ProtonDrive, *common.ProtonDriveCredential, error) {
	/* Log in and logout */
	m, c, credentials, userKR, addrKRs, addrData, err := common.Login(ctx, config, authHandler, deAuthHandler)
	if err != nil {
		return nil, nil, err
	}

	/*
		Current understanding (at the time of the commit)

		The volume is the mount point.

		A link is like a folder in POSIX.

		A share is associated with a link to represent the access control,
		and serves as an entry point to a location in the file structure (Volume).
		It points to a link, of file or folder type, anywhere in the tree and holds a key called the ShareKey.
		To access a link, of file or folder type, a user must be a member of a share.

		A volume has a default share for access control and is owned by the creator of the volume.
		A volume has a default link as it's root folder.

		MIMETYPE holds type, e.g. folder, image/png, etc.
	*/
	volumes, err := listAllVolumes(ctx, c)
	if err != nil {
		return nil, nil, err
	}
	// log.Printf("all volumes %#v", volumes)

	mainShareID := ""
	for i := range volumes {
		// iOS drive: first active volume
		if volumes[i].State == proton.VolumeStateActive {
			mainShareID = volumes[i].Share.ShareID
		}
	}
	// log.Println("total volumes", len(volumes), "mainShareID", mainShareID)

	/* Get root folder from the main share of the volume */
	mainShare, err := getShareByID(ctx, c, mainShareID)
	if err != nil {
		return nil, nil, err
	}

	// check for main share integrity
	{
		mainShareCheck := false
		shares, err := getAllShares(ctx, c)
		if err != nil {
			return nil, nil, err
		}
		for i := range shares {
			if shares[i].ShareID == mainShare.ShareID &&
				shares[i].LinkID == mainShare.LinkID &&
				shares[i].Flags == proton.PrimaryShare &&
				shares[i].Type == proton.ShareTypeMain {
				mainShareCheck = true
			}
		}

		if !mainShareCheck {
			log.Printf("mainShare %#v", mainShare)
			log.Printf("shares %#v", shares)
			return nil, nil, ErrMainSharePreconditionsFailed
		}
	}

	// Note: rootLink's parentLinkID == ""
	/*
		Link holds the tree structure, for the clients, they represent the files and folders of a given volume.
		They have a ParentLinkID that points to parent folders.
		Links also hold the file name (encrypted) and a hash of the name for name collisions.
		Link data is encrypted with its owning Share keyring.
	*/
	rootLink, err := c.GetLink(ctx, mainShare.ShareID, mainShare.LinkID)
	if err != nil {
		return nil, nil, err
	}
	if err != nil {
		return nil, nil, err
	}
	// log.Printf("rootLink %#v", rootLink)

	// log.Printf("addrKRs %#v", addrKRs)=
	mainShareAddrKR := addrKRs[mainShare.AddressID]
	// log.Println("addrKR CountDecryptionEntities", addrKR.CountDecryptionEntities())

	mainShareKR, err := mainShare.GetKeyRing(mainShareAddrKR)
	if err != nil {
		return nil, nil, err
	}
	// log.Println("mainShareKR CountDecryptionEntities", mainShareKR.CountDecryptionEntities())

	return &ProtonDrive{
		MainShare: mainShare,
		RootLink:  &rootLink,

		MainShareKR:   mainShareKR,
		DefaultAddrKR: mainShareAddrKR,

		Config: config,

		c:                c,
		m:                m,
		userKR:           userKR,
		addrKRs:          addrKRs,
		addrData:         addrData,
		signatureAddress: mainShare.Creator,

		cache:                newCache(config.EnableCaching),
		blockUploadSemaphore: semaphore.NewWeighted(int64(config.ConcurrentBlockUploadCount)),
		blockCryptoSemaphore: semaphore.NewWeighted(int64(config.ConcurrentFileCryptoCount)),
	}, credentials, nil
}

func (protonDrive *ProtonDrive) Logout(ctx context.Context) error {
	return common.Logout(ctx, protonDrive.Config, protonDrive.m, protonDrive.c, protonDrive.userKR, protonDrive.addrKRs)
}

func (protonDrive *ProtonDrive) About(ctx context.Context) (*proton.User, error) {
	user, err := protonDrive.c.GetUser(ctx)
	if err != nil {
		return nil, err
	}

	return &user, nil
}

func (protonDrive *ProtonDrive) GetLink(ctx context.Context, linkID string) (*proton.Link, error) {
	return protonDrive.getLink(ctx, linkID)
}

func addKeysFromKR(kr *crypto.KeyRing, newKRs ...*crypto.KeyRing) error {
	for i := range newKRs {
		for _, key := range newKRs[i].GetKeys() {
			err := kr.AddKey(key)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (protonDrive *ProtonDrive) getSignatureVerificationKeyring(emailAddresses []string, verificationAddrKRs ...*crypto.KeyRing) (*crypto.KeyRing, error) {
	ret, err := crypto.NewKeyRing(nil)
	if err != nil {
		return nil, err
	}

	for _, emailAddress := range emailAddresses {
		if addr, ok := protonDrive.addrData[emailAddress]; ok {
			if err := addKeysFromKR(ret, protonDrive.addrKRs[addr.ID]); err != nil {
				return nil, err
			}
		}
	}

	if err := addKeysFromKR(ret, verificationAddrKRs...); err != nil {
		return nil, err
	}

	if ret.CountEntities() == 0 {
		return nil, ErrNoKeyringForSignatureVerification
	}
	return ret, nil
}