Skip to content

Commit af564ca

Browse files
author
Elly Jones
committed
cargo: support optional signing of packages.json files.
1 parent cab4da7 commit af564ca

File tree

4 files changed

+207
-3
lines changed

4 files changed

+207
-3
lines changed

src/cargo/cargo.rc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@
1414
vers = "0.1",
1515
uuid = "9ff87a04-8fed-4295-9ff8-f99bb802650b",
1616
url = "http://rust-lang.org/doc/cargo")];
17+
18+
mod pgp;

src/cargo/cargo.rs

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,14 @@ type package = {
3636
type source = {
3737
name: str,
3838
url: str,
39+
sig: option::t<str>,
40+
key: option::t<str>,
41+
keyfp: option::t<str>,
3942
mutable packages: [package]
4043
};
4144

4245
type cargo = {
46+
pgp: bool,
4347
root: str,
4448
bindir: str,
4549
libdir: str,
@@ -158,12 +162,32 @@ fn need_dir(s: str) {
158162
fn parse_source(name: str, j: json::json) -> source {
159163
alt j {
160164
json::dict(_j) {
161-
alt _j.find("url") {
165+
let url = alt _j.find("url") {
162166
some(json::string(u)) {
163-
ret { name: name, url: u, mutable packages: [] };
167+
u
164168
}
165169
_ { fail "Needed 'url' field in source."; }
166170
};
171+
let sig = alt _j.find("sig") {
172+
some(json::string(u)) {
173+
some(u)
174+
}
175+
_ { none }
176+
};
177+
let key = alt _j.find("key") {
178+
some(json::string(u)) {
179+
some(u)
180+
}
181+
_ { none }
182+
};
183+
let keyfp = alt _j.find("keyfp") {
184+
some(json::string(u)) {
185+
some(u)
186+
}
187+
_ { none }
188+
};
189+
ret { name: name, url: url, sig: sig, key: key, keyfp: keyfp,
190+
mutable packages: [] };
167191
}
168192
_ { fail "Needed dict value in source."; }
169193
};
@@ -269,6 +293,7 @@ fn configure() -> cargo {
269293
try_parse_sources(fs::connect(p, "sources.json"), sources);
270294
try_parse_sources(fs::connect(p, "local-sources.json"), sources);
271295
let c = {
296+
pgp: pgp::supported(),
272297
root: p,
273298
bindir: fs::connect(p, "bin"),
274299
libdir: fs::connect(p, "lib"),
@@ -289,6 +314,10 @@ fn configure() -> cargo {
289314
sources.insert(k, s);
290315
};
291316

317+
if c.pgp {
318+
pgp::init(c.root);
319+
}
320+
292321
c
293322
}
294323

@@ -501,7 +530,10 @@ fn cmd_install(c: cargo, argv: [str]) {
501530

502531
fn sync_one(c: cargo, name: str, src: source) {
503532
let dir = fs::connect(c.sourcedir, name);
504-
let pkgfile = fs::connect(dir, "packages.json");
533+
let pkgfile = fs::connect(dir, "packages.json.new");
534+
let destpkgfile = fs::connect(dir, "packages.json");
535+
let sigfile = fs::connect(dir, "packages.json.sig");
536+
let keyfile = fs::connect(dir, "key.gpg");
505537
let url = src.url;
506538
need_dir(dir);
507539
info(#fmt["fetching source %s...", name]);
@@ -511,6 +543,40 @@ fn sync_one(c: cargo, name: str, src: source) {
511543
} else {
512544
info(#fmt["fetched source: %s", name]);
513545
}
546+
alt src.sig {
547+
some(u) {
548+
let p = run::program_output("curl", ["-f", "-s", "-o", sigfile, u]);
549+
if p.status != 0 {
550+
warn(#fmt["fetch for source %s (sig %s) failed", name, u]);
551+
}
552+
}
553+
_ { }
554+
}
555+
alt src.key {
556+
some(u) {
557+
let p = run::program_output("curl", ["-f", "-s", "-o", keyfile, u]);
558+
if p.status != 0 {
559+
warn(#fmt["fetch for source %s (key %s) failed", name, u]);
560+
}
561+
pgp::add(c.root, keyfile);
562+
}
563+
_ { }
564+
}
565+
alt (src.sig, src.key, src.keyfp) {
566+
(some(_), some(_), some(f)) {
567+
let r = pgp::verify(c.root, pkgfile, sigfile, f);
568+
if !r {
569+
warn(#fmt["signature verification failed for source %s", name]);
570+
ret;
571+
} else {
572+
info(#fmt["signature ok for source %s", name]);
573+
}
574+
}
575+
_ {
576+
info(#fmt["no signature for source %s", name]);
577+
}
578+
}
579+
run::run_program("cp", [pkgfile, destpkgfile]);
514580
}
515581

516582
fn cmd_sync(c: cargo, argv: [str]) {
@@ -523,8 +589,38 @@ fn cmd_sync(c: cargo, argv: [str]) {
523589
}
524590
}
525591

592+
fn cmd_init(c: cargo, argv: [str]) {
593+
let srcurl = "http://www.rust-lang.org/cargo/sources.json";
594+
let sigurl = "http://www.rust-lang.org/cargo/sources.json.sig";
595+
596+
let srcfile = fs::connect(c.root, "sources.json.new");
597+
let sigfile = fs::connect(c.root, "sources.json.sig");
598+
let destsrcfile = fs::connect(c.root, "sources.json");
599+
600+
let p = run::program_output("curl", ["-f", "-s", "-o", srcfile, srcurl]);
601+
if p.status != 0 {
602+
warn(#fmt["fetch of sources.json failed: %s", p.out]);
603+
ret;
604+
}
605+
606+
let p = run::program_output("curl", ["-f", "-s", "-o", sigfile, sigurl]);
607+
if p.status != 0 {
608+
warn(#fmt["fetch of sources.json.sig failed: %s", p.out]);
609+
ret;
610+
}
611+
612+
let r = pgp::verify(c.root, srcfile, sigfile, pgp::signing_key_fp());
613+
if !r {
614+
warn(#fmt["signature verification failed for sources.json"]);
615+
ret;
616+
}
617+
info(#fmt["signature ok for sources.json"]);
618+
run::run_program("cp", [srcfile, destsrcfile]);
619+
}
620+
526621
fn cmd_usage() {
527622
print("Usage: cargo <verb> [args...]");
623+
print(" init Fetch default sources.json");
528624
print(" install [source/]package-name Install by name");
529625
print(" install uuid:[source/]package-uuid Install by uuid");
530626
print(" sync Sync all sources");
@@ -538,6 +634,7 @@ fn main(argv: [str]) {
538634
}
539635
let c = configure();
540636
alt argv[1] {
637+
"init" { cmd_init(c, argv); }
541638
"install" { cmd_install(c, argv); }
542639
"sync" { cmd_sync(c, argv); }
543640
"usage" { cmd_usage(); }

src/cargo/pgp.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use std;
2+
3+
import std::fs;
4+
import std::run;
5+
6+
fn gpg(args: [str]) -> { status: int, out: str, err: str } {
7+
ret run::program_output("gpg", args);
8+
}
9+
10+
fn signing_key() -> str {
11+
"
12+
-----BEGIN PGP PUBLIC KEY BLOCK-----
13+
Version: SKS 1.1.0
14+
15+
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
57+
=2z6P
58+
-----END PGP PUBLIC KEY BLOCK-----
59+
"
60+
}
61+
62+
fn signing_key_fp() -> str {
63+
"FE79 EDB0 3DEF B0D8 27D2 6C41 0B2D 6A28 3033 6376"
64+
}
65+
66+
fn supported() -> bool {
67+
let r = gpg(["--version"]);
68+
r.status == 0
69+
}
70+
71+
fn init(root: str) {
72+
let p = fs::connect(root, "gpg");
73+
if !fs::path_is_dir(p) {
74+
fs::make_dir(p, 0x1c0i32);
75+
let p = run::start_program("gpg", ["--homedir", p, "--import"]);
76+
p.input().write_str(signing_key());
77+
let s = p.finish();
78+
if s != 0 {
79+
fail "pgp init failed";
80+
}
81+
}
82+
}
83+
84+
fn add(root: str, key: str) {
85+
let path = fs::connect(root, "gpg");
86+
let p = run::program_output("gpg", ["--homedir", path, "--import", key]);
87+
if p.status != 0 {
88+
fail "pgp add failed: " + p.out;
89+
}
90+
}
91+
92+
fn verify(root: str, data: str, sig: str, keyfp: str) -> bool {
93+
let path = fs::connect(root, "gpg");
94+
let p = gpg(["--homedir", path, "--with-fingerprint", "--verify", sig, data]);
95+
let res = "Primary key fingerprint: " + keyfp;
96+
for line in str::split(p.err, '\n' as u8) {
97+
if line == res {
98+
ret true;
99+
}
100+
}
101+
ret false;
102+
}

src/cargo/sources.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"elly": {
33
"url": "https://raw.github.com/elly/rust-packages/master/packages.json"
4+
"sig": "https://raw.github.com/elly/rust-packages/master/packages.json.sig",
5+
"key": "https://raw.github.com/elly/rust-packages/master/signing-key.gpg",
6+
"keyfp": "4107 21C0 FF32 858F 61FF 33F6 E595 8E36 FDC8 EA00"
47
},
58
"brson": {
69
"url": "https://raw.github.com/brson/rust-packages/master/packages.json"

0 commit comments

Comments
 (0)