Skip to content

Commit aa933c2

Browse files
committed
host encrypted data inside main repository
With those changes we no longer need an extra repository for hosting the encrypted data.
1 parent 83a4c9c commit aa933c2

File tree

3 files changed

+69
-66
lines changed

3 files changed

+69
-66
lines changed

.github/workflows/test.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ jobs:
66
strategy:
77
matrix:
88
os:
9-
- ubuntu-20.04
10-
#- ubuntu-20.04-arm
119
- ubuntu-22.04
1210
- ubuntu-22.04-arm
1311
- ubuntu-24.04

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ test: testman
3737
git -C crypt init --bare -b _
3838
git ls-remote $(TESTREPO)
3939
git incrypt init $(REPO) $(KEY)
40-
git -C $(TESTREPO) fetch $(REPO) || git -C $(TESTREPO) incrypt trust $(REPO)
40+
git -C $(TESTREPO) fetch $(VERBOSE) $(REPO) || git -C $(TESTREPO) incrypt trust $(REPO)
4141
git -C $(TESTREPO) push $(VERBOSE) $(REPO) HEAD~2:refs/heads/master
4242
git clone $(VERBOSE) $(REPO) tst
4343
git -C $(TESTREPO) push $(VERBOSE) $(REPO) v0.9.0

git-incrypt

Lines changed: 68 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def encryptrefname(ref, key):
102102

103103
sref = splitref(ref)
104104
rawname = sref[0].encode('utf-8')
105-
return 'refs/heads/' + base64.b64encode(encryptdata(
105+
return base64.b64encode(encryptdata(
106106
hashlib.sha1(rawname).digest() + rawname, key),
107107
b'+#').decode('utf-8') + sref[1]
108108

@@ -132,33 +132,20 @@ class CryptRepo:
132132
'followtags': False
133133
}
134134
hashstr = hashlib.sha1(url.encode('utf-8')).hexdigest()
135-
self.refprefix = f'refs/incrypt/{hashstr}/'
135+
self.prefix = f'refs/incrypt/{hashstr}/'
136+
self.url = url
136137
if init:
137138
self.repo = pygit2.init_repository(clearname, bare=True)
138-
self.repo.remotes.create('incrypt', url)
139139
template = self._mktemplate(init.name, init.email,
140140
init.date, init.m)
141-
self.meta = MetaData(self.repo).init(init.keys, template,
142-
'refs/heads/master')
141+
self.meta = MetaData(self.repo, self.url, self.prefix + '1/').init(
142+
init.keys, template, 'refs/heads/master')
143143
self.trust(force=True, sign=True)
144-
self.clearrepo = pygit2.Repository(clearname)
145144
else:
146-
self.clearrepo = pygit2.Repository(clearname)
147-
name = os.path.join(self.clearrepo.path, 'incrypt', hashstr)
148-
if not os.path.isdir(name):
149-
subprocess.run(
150-
['git', 'clone',
151-
CryptRepo.verbosityflags[self.settings['verbosity']],
152-
'--progress' if self.settings['progress']
153-
else '--no-progress',
154-
'-o', 'incrypt', '-c', 'fetch.prune=true',
155-
'--mirror', url, name],
156-
check=True, stdout=sys.stderr)
157-
subprocess.run(
158-
['git', 'config', 'unset', 'remote.incrypt.mirror'],
159-
cwd=name, check=True, stdout=sys.stderr)
160-
self.repo = pygit2.Repository(name)
161-
self.meta = MetaData(self.repo).read()
145+
self.repo = pygit2.Repository(clearname)
146+
self._fetch('_')
147+
self.meta = MetaData(self.repo, self.url,
148+
self.prefix + '1/').read()
162149
if forcetrust:
163150
self.trust(force=forcetrust)
164151

@@ -228,16 +215,25 @@ class CryptRepo:
228215
([f'^{x}\n' for x in excl] if excl else [])))
229216
return text.strip().split('\n') if text else []
230217

218+
def _fetch(self, pattern):
219+
subprocess.run(
220+
['git', 'fetch',
221+
CryptRepo.verbosityflags[self.settings['verbosity']],
222+
'--progress' if self.settings['progress'] else '--no-progress',
223+
'--no-write-fetch-head', '-p', self.url,
224+
f'+refs/heads/{pattern}:{self.prefix}1/{pattern}'],
225+
cwd=self.repo.path, check=True, stdout=sys.stderr)
226+
231227
def trust(self, force=False, sign=False):
232228
'trust this repository'
233229
if force:
234230
trusted = True
235231
else:
236232
try:
237-
with open(os.path.join(self.repo.path, 'incrypt-keyhash'), 'r',
238-
encoding='utf-8') as file:
239-
expectedhash = file.read().strip()
240-
except FileNotFoundError:
233+
expectedhash = self.repo.revparse_single(
234+
self.prefix +
235+
'keyhash').tree['_'].read_raw().decode('utf-8')
236+
except KeyError:
241237
expectedhash = None
242238
if expectedhash:
243239
assert expectedhash == self.meta.keyhash, \
@@ -248,53 +244,56 @@ class CryptRepo:
248244
assert trusted, 'Key is not trusted'
249245
if sign:
250246
self.meta.sign()
251-
with open(os.path.join(self.repo.path, 'incrypt-keyhash'), 'w',
252-
encoding='utf-8') as file:
253-
file.write(self.meta.keyhash)
247+
collector = self.repo.TreeBuilder()
248+
collector.insert('_', self.repo.create_blob(self.meta.keyhash),
249+
pygit2.enums.FileMode.BLOB)
250+
colid = collector.write()
251+
commit = self.meta.secretcommit(
252+
colid, [])
253+
self.repo.create_reference(self.prefix + 'keyhash', commit, force=True)
254254

255255
def getrefs(self):
256256
'list all cleartext references'
257257
def decryptobject(oid):
258258
'decrypt an object'
259259
raw = decryptdata(self.repo.get(oid).read_raw(), self.meta.key)
260-
return self.clearrepo.odb.write(raw[20], raw[21:])
261-
subprocess.run(
262-
['git', 'fetch',
263-
CryptRepo.verbosityflags[self.settings['verbosity']],
264-
'--progress' if self.settings['progress'] else '--no-progress'],
265-
cwd=self.repo.path, check=True, stdout=sys.stderr)
260+
return self.repo.odb.write(raw[20], raw[21:])
261+
self._fetch('*')
266262
self.meta.read()
267263
self.trust()
268264
# [ dec(crypt) , prefdec(crypt), crypt ]
269-
refs = [[r[0], self.refprefix + r[0], self.repo.revparse_single(r[1])]
265+
refs = [[r[0], self.prefix + '0/' + r[0],
266+
self.repo.revparse_single(r[1])]
270267
for r in [[decryptrefname(r, self.meta.key), r] for r in
271-
filter(lambda r: len(r) > 14, self.repo.references)]]
272-
cryptmap = self.meta.readmap(reverse=self.clearrepo)
268+
filter(lambda r: len(r) > len(self.prefix)+3 and
269+
r.startswith(self.prefix + '1/'),
270+
self.repo.references)]]
271+
cryptmap = self.meta.readmap(reverse=True)
273272
self._progress_for(
274273
'Decrypting objects',
275274
CryptRepo._revlist(CryptRepo.RevMode.BLOB, [r[2].id for r in refs],
276275
cryptmap, cwd=self.repo.path),
277276
decryptobject)
278277
for r in refs:
279-
self.clearrepo.create_reference(
278+
self.repo.create_reference(
280279
r[1], decryptdata(r[2].tree['0'].read_raw(),
281280
self.meta.key)[0:20].hex(), force=True)
282281
expected = [r[1] for r in refs]
283282
result = [['HEAD', f'@{self.meta.defaultbranch}']]
284-
for r in self.clearrepo.references:
285-
if r.startswith(self.refprefix):
283+
for r in self.repo.references:
284+
if r.startswith(self.prefix + '0/'):
286285
if r in expected:
287-
result.append([r[len(self.refprefix):],
288-
self.clearrepo.lookup_reference(r).target])
286+
result.append([r[len(self.prefix)+2:],
287+
self.repo.lookup_reference(r).target])
289288
else:
290-
self.clearrepo.references.delete(r)
289+
self.repo.references.delete(r)
291290
return result
292291

293292
def push(self, refs):
294293
'push refs'
295294
def encryptobject(oid):
296295
'encrypt an object'
297-
clear = self.clearrepo.get(oid)
296+
clear = self.repo.get(oid)
298297
cryptobjs[clear.id] = self.repo.create_blob(encryptdata(
299298
clear.id.raw + clear.type.to_bytes(1, byteorder='big') +
300299
clear.read_raw(), self.meta.key))
@@ -314,7 +313,7 @@ class CryptRepo:
314313
collectorinsert(collector, cryptobjs[obj.id])
315314
collectorinsert(collector, cryptobjs[tree.id])
316315

317-
obj = self.clearrepo.get(oid)
316+
obj = self.repo.get(oid)
318317
collector = self.repo.TreeBuilder()
319318
if obj.type == pygit2.enums.ObjectType.COMMIT:
320319
encrypttree(obj.tree)
@@ -329,11 +328,13 @@ class CryptRepo:
329328
if obj.type == pygit2.enums.ObjectType.COMMIT
330329
else [cryptmap[str(obj.target)]])
331330

332-
xrefs = [[r[0], r[1], r[3],
333-
f"{'+' if r[2] else ''}{r[3] if r[0] else ''}:{r[3]}",
334-
self.clearrepo.revparse_single(r[0]) if r[0] else None]
335-
for r in [[r[0], r[1], r[2], encryptrefname(r[1],
336-
self.meta.key)] for r in refs]]
331+
xrefs = [[r[0], r[1], r[3], ('+' if r[2] else '') +
332+
(self.prefix + '1/' + r[3] if r[0] else '') +
333+
':refs/heads/' + r[3],
334+
self.repo.revparse_single(r[0]) if r[0] else None]
335+
for r in [[r[0], r[1], r[2],
336+
encryptrefname(r[1], self.meta.key)]
337+
for r in refs]]
337338
cryptmap = self.meta.readmap()
338339
colobjs = CryptRepo._revlist(
339340
CryptRepo.RevMode.PARENT, [r[4].id for r in xrefs if r[4]],
@@ -346,11 +347,12 @@ class CryptRepo:
346347
self.meta.write(cryptmap)
347348
for r in xrefs:
348349
if r[4]:
349-
self.repo.create_reference(r[2], cryptmap[str(r[4].id)],
350-
force=True)
350+
self.repo.create_reference(
351+
self.prefix + '1/' + r[2], cryptmap[str(r[4].id)],
352+
force=True)
351353
else:
352354
try:
353-
self.repo.references.delete(r[2])
355+
self.repo.references.delete(self.prefix + '1/' + r[2])
354356
except KeyError:
355357
pass
356358
resdict = {}
@@ -361,7 +363,7 @@ class CryptRepo:
361363
['--progress' if self.settings['progress']
362364
else '--no-progress', '--porcelain'] +
363365
(['--atomic'] if self.settings['atomic'] else []) +
364-
['incrypt', f'+{MetaData.REFNAME}:{MetaData.REFNAME}'] +
366+
[self.url, '+' + self.prefix + '1/_:' + MetaData.REFNAME] +
365367
[r[3] for r in xrefs],
366368
cwd=self.repo.path, check=False, text=True,
367369
stdout=subprocess.PIPE,
@@ -372,7 +374,7 @@ class CryptRepo:
372374
if len(e) == 3:
373375
resdict[e[1].split(':', 1)[1]] = \
374376
e[2].split('(', 1)[1][0:-1] if e[0] == '!' else None
375-
return {r[1]: resdict[r[2]] for r in xrefs}
377+
return {r[1]: resdict['refs/heads/' + r[2]] for r in xrefs}
376378

377379

378380
def remotehelperloop():
@@ -433,13 +435,16 @@ def remotehelperloop():
433435

434436

435437
class MetaData:
438+
# pylint: disable=too-many-instance-attributes
436439
'container for meta data'
437440
VER = b'git-incrypt\n1.0.0\n'
438441
KEYVER = b'AES-256-CBC+IV'
439442
REFNAME = 'refs/heads/_'
440443

441-
def __init__(self, repo):
444+
def __init__(self, repo, url, prefix):
442445
self.repo = repo
446+
self.url = url
447+
self.prefix = prefix
443448
self.files = None
444449
self.key = None
445450
self.keyhash = None
@@ -450,7 +455,7 @@ class MetaData:
450455
def _gpg(self, args, inp):
451456
'run gpg'
452457
return subprocess.check_output(
453-
[f'gpg@incrypt::{self.repo.remotes["incrypt"].url}'] + args,
458+
[f'gpg@incrypt::{self.url}'] + args,
454459
executable='gpg', input=inp)
455460

456461
def init(self, gpgkeys, template, defaultbranch):
@@ -485,7 +490,7 @@ class MetaData:
485490
def read(self):
486491
'read the metadata'
487492
self.files = {}
488-
tree = self.repo.revparse_single(MetaData.REFNAME).tree
493+
tree = self.repo.revparse_single(self.prefix + '_').tree
489494
obj = tree['ver']
490495
self.files['ver'] = obj.id
491496
data = obj.read_raw()
@@ -582,18 +587,18 @@ class MetaData:
582587
collector.insert('README.md', readmefile, pygit2.enums.FileMode.BLOB)
583588
colid = collector.write()
584589
commit = self.secretcommit(colid, [])
585-
self.repo.create_reference(MetaData.REFNAME, commit, force=True)
590+
self.repo.create_reference(self.prefix + '_', commit, force=True)
586591
return self
587592

588-
def readmap(self, reverse=None):
593+
def readmap(self, reverse=False):
589594
'read the mapping table'
590-
tree = self.repo.revparse_single(MetaData.REFNAME).tree
595+
tree = self.repo.revparse_single(self.prefix + '_').tree
591596
o = 20 if reverse else 0
592597
processed = {}
593598
rawdata = decryptdata(tree['map'].read_raw(), self.key)
594599
for i in range(20, len(rawdata), 40):
595600
target = rawdata[i+20-o:i+40-o].hex()
596-
if target in (reverse if reverse else self.repo):
601+
if target in self.repo:
597602
processed[rawdata[i+o:i+20+o].hex()] = target
598603
return processed
599604

0 commit comments

Comments
 (0)