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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
|
/******************************************************************************
__ ____ __
/ / ___ ____ _/ __/_____________ _/ /__
/ / / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \
/ /___/ __/ /_/ / __(__ ) /__/ /_/ / / __/
/_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/
(C)opyright 2025, Leafscale, LLC - https://www.leafscale.com
Project: Zygaena
Filename: signing.reef
Authors: Chris Tusa <chris.tusa@leafscale.com>
License: <see LICENSE file included with this source code>
Description: Ed25519 signing and verification for packages and repositories
******************************************************************************/
module core.signing
import core.str
import io.file
import io.dir
import io.path
import sys.process
export
type KeyPair
type SignResult
type VerifyResult
fn generate_keypair(name: string): SignResult
fn sign_file(file_path: string, key_path: string): SignResult
fn verify_file(file_path: string, sig_path: string, key_path: string): VerifyResult
fn import_key(key_data: string, name: string): SignResult
fn list_keys(keys: [KeyPair], max_count: int): int
fn get_keyring_dir(): string
fn get_trusted_keys_dir(): string
// High-level verification functions
fn verify_package(pkg_path: string, sig_path: string): VerifyResult
fn verify_manifest(manifest_path: string): VerifyResult
fn is_key_trusted(keyid: string): bool
end export
// Key pair information
type KeyPair = struct
name: string
keyid: string
public_key: string
private_key: string
created: string
trusted: bool
end KeyPair
// Result for signing operations
type SignResult = struct
success: bool
signature: string
error: string
end SignResult
// Result for verification
type VerifyResult = struct
success: bool
valid: bool
keyid: string
error: string
end VerifyResult
// Get the keyring directory
fn get_keyring_dir(): string
return "/etc/coral/keys"
end get_keyring_dir
// Create empty keypair
fn new_keypair(): KeyPair
return KeyPair{ name: "", keyid: "", public_key: "", private_key: "", created: "", trusted: false }
end new_keypair
// Create error result
fn error_sign_result(msg: string): SignResult
return SignResult{ success: false, signature: "", error: msg }
end error_sign_result
fn error_verify_result(msg: string): VerifyResult
return VerifyResult{ success: false, valid: false, keyid: "", error: msg }
end error_verify_result
// Generate a new Ed25519 keypair
// Note: This requires an external tool (openssl or signify)
fn generate_keypair(name: string): SignResult
let keyring = get_keyring_dir()
// Ensure keyring directory exists
if not dir.dir_exists(keyring)
if not dir.create_dir_all(keyring)
return error_sign_result("Failed to create keyring directory")
end if
end if
let private_path = path.join_path(keyring, name + ".key")
let public_path = path.join_path(keyring, name + ".pub")
// Check if key already exists
if file.fileExists(private_path)
return error_sign_result("Key already exists: " + name)
end if
// Generate key using openssl
let cmd = "openssl genpkey -algorithm ed25519 -out \"" + private_path + "\" && " +
"openssl pkey -in \"" + private_path + "\" -pubout -out \"" + public_path + "\""
let pid = process.process_spawn_shell(cmd)
if pid < 0
return error_sign_result("Failed to start key generation")
end if
let exit_code = process.process_wait(pid)
if exit_code != 0
return error_sign_result("Key generation failed")
end if
return SignResult{ success: true, signature: "", error: "" }
end generate_keypair
// Sign a file with Ed25519
fn sign_file(file_path: string, key_path: string): SignResult
if not file.fileExists(file_path)
return error_sign_result("File not found: " + file_path)
end if
if not file.fileExists(key_path)
return error_sign_result("Key not found: " + key_path)
end if
let sig_path = file_path + ".sig"
// Sign using openssl
let cmd = "openssl pkeyutl -sign -inkey \"" + key_path + "\" -in \"" + file_path + "\" -out \"" + sig_path + "\""
let pid = process.process_spawn_shell(cmd)
if pid < 0
return error_sign_result("Failed to start signing")
end if
let exit_code = process.process_wait(pid)
if exit_code != 0
return error_sign_result("Signing failed")
end if
return SignResult{ success: true, signature: sig_path, error: "" }
end sign_file
// Verify a file signature
fn verify_file(file_path: string, sig_path: string, key_path: string): VerifyResult
if not file.fileExists(file_path)
return error_verify_result("File not found: " + file_path)
end if
if not file.fileExists(sig_path)
return error_verify_result("Signature not found: " + sig_path)
end if
if not file.fileExists(key_path)
return error_verify_result("Key not found: " + key_path)
end if
// Verify using openssl
let cmd = "openssl pkeyutl -verify -pubin -inkey \"" + key_path + "\" -in \"" + file_path + "\" -sigfile \"" + sig_path + "\""
let pid = process.process_spawn_shell(cmd)
if pid < 0
return error_verify_result("Failed to start verification")
end if
let exit_code = process.process_wait(pid)
if exit_code == 0
return VerifyResult{ success: true, valid: true, keyid: "", error: "" }
else
return VerifyResult{ success: true, valid: false, keyid: "", error: "Signature verification failed" }
end if
end verify_file
// Import a public key
fn import_key(key_data: string, name: string): SignResult
let keyring = get_keyring_dir()
if not dir.dir_exists(keyring)
if not dir.create_dir_all(keyring)
return error_sign_result("Failed to create keyring directory")
end if
end if
let key_path = path.join_path(keyring, name + ".pub")
if file.fileExists(key_path)
return error_sign_result("Key already exists: " + name)
end if
if not file.writeFile(key_path, key_data)
return error_sign_result("Failed to write key file")
end if
return SignResult{ success: true, signature: "", error: "" }
end import_key
// List all keys in keyring
fn list_keys(keys: [KeyPair], max_count: int): int
let keyring = get_keyring_dir()
if not dir.dir_exists(keyring)
return 0
end if
mut entries: [string] = new [string](64)
let entry_count = dir.list_dir(keyring, entries, 64)
mut count = 0
mut i = 0
while i < entry_count and count < max_count
let entry = entries[i]
// Look for .pub files
if str.ends_with(entry, ".pub")
let name_len = str.length(entry) - 4
let name = str.substring(entry, 0, name_len)
mut kp = new_keypair()
kp.name = name
kp.trusted = true // Local keys are trusted
// Read public key
let pub_path = path.join_path(keyring, entry)
kp.public_key = file.readFile(pub_path)
// Check for private key
let priv_path = path.join_path(keyring, name + ".key")
if file.fileExists(priv_path)
kp.private_key = "(present)"
end if
keys[count] = kp
count = count + 1
end if
i = i + 1
end while
return count
end list_keys
// Get the trusted keys directory
fn get_trusted_keys_dir(): string
return "/var/lib/coral/trusted-keys"
end get_trusted_keys_dir
// Check if a key is in the trusted keys list
fn is_key_trusted(keyid: string): bool
let trusted_dir = get_trusted_keys_dir()
if not dir.dir_exists(trusted_dir)
return false
end if
// Look for key file by keyid
let key_path = path.join_path(trusted_dir, keyid + ".pub")
if file.fileExists(key_path)
return true
end if
// Also check the main keyring
let keyring_path = path.join_path(get_keyring_dir(), keyid + ".pub")
return file.fileExists(keyring_path)
end is_key_trusted
// Get path to a trusted key by keyid
fn get_trusted_key_path(keyid: string): string
// Try trusted-keys directory first
let trusted_dir = get_trusted_keys_dir()
let trusted_path = path.join_path(trusted_dir, keyid + ".pub")
if file.fileExists(trusted_path)
return trusted_path
end if
// Fall back to main keyring
let keyring_path = path.join_path(get_keyring_dir(), keyid + ".pub")
if file.fileExists(keyring_path)
return keyring_path
end if
return ""
end get_trusted_key_path
// Verify a package with any trusted key
fn verify_package(pkg_path: string, sig_path: string): VerifyResult
if not file.fileExists(pkg_path)
return error_verify_result("Package not found: " + pkg_path)
end if
if not file.fileExists(sig_path)
return error_verify_result("Signature not found: " + sig_path)
end if
// Try to verify with each trusted key
let trusted_dir = get_trusted_keys_dir()
let keyring_dir = get_keyring_dir()
// Collect all public keys from both directories
mut all_keys: [string] = new [string](64)
mut key_count = 0
// Get keys from trusted-keys directory
if dir.dir_exists(trusted_dir)
mut entries: [string] = new [string](32)
let entry_count = dir.list_dir(trusted_dir, entries, 32)
mut i = 0
while i < entry_count and key_count < 64
if str.ends_with(entries[i], ".pub")
all_keys[key_count] = path.join_path(trusted_dir, entries[i])
key_count = key_count + 1
end if
i = i + 1
end while
end if
// Get keys from keyring directory
if dir.dir_exists(keyring_dir)
mut entries: [string] = new [string](32)
let entry_count = dir.list_dir(keyring_dir, entries, 32)
mut i = 0
while i < entry_count and key_count < 64
if str.ends_with(entries[i], ".pub")
let key_path = path.join_path(keyring_dir, entries[i])
// Avoid duplicates
mut is_dup = false
mut j = 0
while j < key_count
if str.equals(all_keys[j], key_path)
is_dup = true
end if
j = j + 1
end while
if not is_dup
all_keys[key_count] = key_path
key_count = key_count + 1
end if
end if
i = i + 1
end while
end if
if key_count == 0
return error_verify_result("No trusted keys found")
end if
// Try each key
mut i = 0
while i < key_count
let key_path = all_keys[i]
let result = verify_file(pkg_path, sig_path, key_path)
if result.success and result.valid
// Extract keyid from path
let filename = path.basename(key_path)
let keyid = str.substring(filename, 0, str.length(filename) - 4)
return VerifyResult{ success: true, valid: true, keyid: keyid, error: "" }
end if
i = i + 1
end while
return VerifyResult{ success: true, valid: false, keyid: "", error: "Signature does not match any trusted key" }
end verify_package
// Verify a repository manifest (repo.toml and repo.toml.sig)
fn verify_manifest(manifest_path: string): VerifyResult
if not file.fileExists(manifest_path)
return error_verify_result("Manifest not found: " + manifest_path)
end if
let sig_path = manifest_path + ".sig"
if not file.fileExists(sig_path)
return error_verify_result("Manifest signature not found: " + sig_path)
end if
return verify_package(manifest_path, sig_path)
end verify_manifest
end module
|