diff --git a/src/cargo/cargo.rc b/src/cargo/cargo.rc index 54267c570fe..3acca318f33 100644 --- a/src/cargo/cargo.rc +++ b/src/cargo/cargo.rc @@ -14,3 +14,5 @@ vers = "0.1", uuid = "9ff87a04-8fed-4295-9ff8-f99bb802650b", url = "http://rust-lang.org/doc/cargo")]; + +mod pgp; diff --git a/src/cargo/cargo.rs b/src/cargo/cargo.rs index fc06bd1581b..8972a38d1f3 100644 --- a/src/cargo/cargo.rs +++ b/src/cargo/cargo.rs @@ -36,10 +36,14 @@ type package = { type source = { name: str, url: str, + sig: option::t, + key: option::t, + keyfp: option::t, mutable packages: [package] }; type cargo = { + pgp: bool, root: str, bindir: str, libdir: str, @@ -158,12 +162,32 @@ fn need_dir(s: str) { fn parse_source(name: str, j: json::json) -> source { alt j { json::dict(_j) { - alt _j.find("url") { + let url = alt _j.find("url") { some(json::string(u)) { - ret { name: name, url: u, mutable packages: [] }; + u } _ { fail "Needed 'url' field in source."; } }; + let sig = alt _j.find("sig") { + some(json::string(u)) { + some(u) + } + _ { none } + }; + let key = alt _j.find("key") { + some(json::string(u)) { + some(u) + } + _ { none } + }; + let keyfp = alt _j.find("keyfp") { + some(json::string(u)) { + some(u) + } + _ { none } + }; + ret { name: name, url: url, sig: sig, key: key, keyfp: keyfp, + mutable packages: [] }; } _ { fail "Needed dict value in source."; } }; @@ -269,6 +293,7 @@ fn configure() -> cargo { try_parse_sources(fs::connect(p, "sources.json"), sources); try_parse_sources(fs::connect(p, "local-sources.json"), sources); let c = { + pgp: pgp::supported(), root: p, bindir: fs::connect(p, "bin"), libdir: fs::connect(p, "lib"), @@ -289,6 +314,10 @@ fn configure() -> cargo { sources.insert(k, s); }; + if c.pgp { + pgp::init(c.root); + } + c } @@ -501,7 +530,10 @@ fn cmd_install(c: cargo, argv: [str]) { fn sync_one(c: cargo, name: str, src: source) { let dir = fs::connect(c.sourcedir, name); - let pkgfile = fs::connect(dir, "packages.json"); + let pkgfile = fs::connect(dir, "packages.json.new"); + let destpkgfile = fs::connect(dir, "packages.json"); + let sigfile = fs::connect(dir, "packages.json.sig"); + let keyfile = fs::connect(dir, "key.gpg"); let url = src.url; need_dir(dir); info(#fmt["fetching source %s...", name]); @@ -511,6 +543,40 @@ fn sync_one(c: cargo, name: str, src: source) { } else { info(#fmt["fetched source: %s", name]); } + alt src.sig { + some(u) { + let p = run::program_output("curl", ["-f", "-s", "-o", sigfile, u]); + if p.status != 0 { + warn(#fmt["fetch for source %s (sig %s) failed", name, u]); + } + } + _ { } + } + alt src.key { + some(u) { + let p = run::program_output("curl", ["-f", "-s", "-o", keyfile, u]); + if p.status != 0 { + warn(#fmt["fetch for source %s (key %s) failed", name, u]); + } + pgp::add(c.root, keyfile); + } + _ { } + } + alt (src.sig, src.key, src.keyfp) { + (some(_), some(_), some(f)) { + let r = pgp::verify(c.root, pkgfile, sigfile, f); + if !r { + warn(#fmt["signature verification failed for source %s", name]); + ret; + } else { + info(#fmt["signature ok for source %s", name]); + } + } + _ { + info(#fmt["no signature for source %s", name]); + } + } + run::run_program("cp", [pkgfile, destpkgfile]); } fn cmd_sync(c: cargo, argv: [str]) { @@ -523,8 +589,38 @@ fn cmd_sync(c: cargo, argv: [str]) { } } +fn cmd_init(c: cargo, argv: [str]) { + let srcurl = "http://www.rust-lang.org/cargo/sources.json"; + let sigurl = "http://www.rust-lang.org/cargo/sources.json.sig"; + + let srcfile = fs::connect(c.root, "sources.json.new"); + let sigfile = fs::connect(c.root, "sources.json.sig"); + let destsrcfile = fs::connect(c.root, "sources.json"); + + let p = run::program_output("curl", ["-f", "-s", "-o", srcfile, srcurl]); + if p.status != 0 { + warn(#fmt["fetch of sources.json failed: %s", p.out]); + ret; + } + + let p = run::program_output("curl", ["-f", "-s", "-o", sigfile, sigurl]); + if p.status != 0 { + warn(#fmt["fetch of sources.json.sig failed: %s", p.out]); + ret; + } + + let r = pgp::verify(c.root, srcfile, sigfile, pgp::signing_key_fp()); + if !r { + warn(#fmt["signature verification failed for sources.json"]); + ret; + } + info(#fmt["signature ok for sources.json"]); + run::run_program("cp", [srcfile, destsrcfile]); +} + fn cmd_usage() { print("Usage: cargo [args...]"); + print(" init Fetch default sources.json"); print(" install [source/]package-name Install by name"); print(" install uuid:[source/]package-uuid Install by uuid"); print(" sync Sync all sources"); @@ -538,6 +634,7 @@ fn main(argv: [str]) { } let c = configure(); alt argv[1] { + "init" { cmd_init(c, argv); } "install" { cmd_install(c, argv); } "sync" { cmd_sync(c, argv); } "usage" { cmd_usage(); } diff --git a/src/cargo/pgp.rs b/src/cargo/pgp.rs new file mode 100644 index 00000000000..be6b47e66fb --- /dev/null +++ b/src/cargo/pgp.rs @@ -0,0 +1,102 @@ +use std; + +import std::fs; +import std::run; + +fn gpg(args: [str]) -> { status: int, out: str, err: str } { + ret run::program_output("gpg", args); +} + +fn signing_key() -> str { + " +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: SKS 1.1.0 + +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 +=2z6P +-----END PGP PUBLIC KEY BLOCK----- +" +} + +fn signing_key_fp() -> str { + "FE79 EDB0 3DEF B0D8 27D2 6C41 0B2D 6A28 3033 6376" +} + +fn supported() -> bool { + let r = gpg(["--version"]); + r.status == 0 +} + +fn init(root: str) { + let p = fs::connect(root, "gpg"); + if !fs::path_is_dir(p) { + fs::make_dir(p, 0x1c0i32); + let p = run::start_program("gpg", ["--homedir", p, "--import"]); + p.input().write_str(signing_key()); + let s = p.finish(); + if s != 0 { + fail "pgp init failed"; + } + } +} + +fn add(root: str, key: str) { + let path = fs::connect(root, "gpg"); + let p = run::program_output("gpg", ["--homedir", path, "--import", key]); + if p.status != 0 { + fail "pgp add failed: " + p.out; + } +} + +fn verify(root: str, data: str, sig: str, keyfp: str) -> bool { + let path = fs::connect(root, "gpg"); + let p = gpg(["--homedir", path, "--with-fingerprint", "--verify", sig, data]); + let res = "Primary key fingerprint: " + keyfp; + for line in str::split(p.err, '\n' as u8) { + if line == res { + ret true; + } + } + ret false; +} diff --git a/src/cargo/sources.json b/src/cargo/sources.json index 48c29dca9c1..8d0ed27860b 100644 --- a/src/cargo/sources.json +++ b/src/cargo/sources.json @@ -1,6 +1,9 @@ { "elly": { "url": "https://raw.github.com/elly/rust-packages/master/packages.json" + "sig": "https://raw.github.com/elly/rust-packages/master/packages.json.sig", + "key": "https://raw.github.com/elly/rust-packages/master/signing-key.gpg", + "keyfp": "4107 21C0 FF32 858F 61FF 33F6 E595 8E36 FDC8 EA00" }, "brson": { "url": "https://raw.github.com/brson/rust-packages/master/packages.json"