Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cargo: support optional signing of packages.json files. #1363

Merged
merged 3 commits into from
Dec 21, 2011
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/cargo/cargo.rc
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@
vers = "0.1",
uuid = "9ff87a04-8fed-4295-9ff8-f99bb802650b",
url = "http://rust-lang.org/doc/cargo")];

mod pgp;
165 changes: 158 additions & 7 deletions src/cargo/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,21 @@ type package = {
name: str,
uuid: str,
url: str,
method: str
method: str,
tags: [str]
};

type source = {
name: str,
url: str,
sig: option::t<str>,
key: option::t<str>,
keyfp: option::t<str>,
mutable packages: [package]
};

type cargo = {
pgp: bool,
root: str,
bindir: str,
libdir: str,
Expand Down Expand Up @@ -158,12 +163,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."; }
};
Expand Down Expand Up @@ -217,18 +242,31 @@ fn load_one_source_package(&src: source, p: map::hashmap<str, json::json>) {
}
};

let tags = [];
alt p.find("tags") {
some(json::list(js)) {
for j in *js {
alt j {
json::string(_j) { vec::grow(tags, 1u, _j); }
_ { }
}
}
}
_ { }
}
vec::grow(src.packages, 1u, {
// source: _source(src),
name: name,
uuid: uuid,
url: url,
method: method
method: method,
tags: tags
});
info(" Loaded package: " + src.name + "/" + name);
log " Loaded package: " + src.name + "/" + name;
}

fn load_source_packages(&c: cargo, &src: source) {
info("Loading source: " + src.name);
log "Loading source: " + src.name;
let dir = fs::connect(c.sourcedir, src.name);
let pkgfile = fs::connect(dir, "packages.json");
if !fs::path_exists(pkgfile) { ret; }
Expand Down Expand Up @@ -269,6 +307,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"),
Expand All @@ -289,6 +328,10 @@ fn configure() -> cargo {
sources.insert(k, s);
};

if c.pgp {
pgp::init(c.root);
}

c
}

Expand Down Expand Up @@ -501,7 +544,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]);
Expand All @@ -511,6 +557,43 @@ 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]) {
Expand All @@ -523,10 +606,75 @@ fn cmd_sync(c: cargo, argv: [str]) {
}
}

fn cmd_init(c: cargo) {
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 print_pkg(s: source, p: package) {
let m = s.name + "/" + p.name + " (" + p.uuid + ")";
if vec::len(p.tags) > 0u {
m = m + " [" + str::connect(p.tags, ", ") + "]";
}
info(m);
}
fn cmd_list(c: cargo, argv: [str]) {
for_each_package(c, { |s, p|
if vec::len(argv) <= 2u || argv[2] == s.name {
print_pkg(s, p);
}
});
}

fn cmd_search(c: cargo, argv: [str]) {
if vec::len(argv) < 3u {
cmd_usage();
ret;
}
let n = 0;
let name = argv[2];
let tags = vec::slice(argv, 3u, vec::len(argv));
for_each_package(c, { |s, p|
if (str::contains(p.name, name) || name == "*") &&
vec::all(tags, { |t| vec::member(t, p.tags) }) {
print_pkg(s, p);
n += 1;
}
});
info(#fmt["Found %d packages.", n]);
}

fn cmd_usage() {
print("Usage: cargo <verb> [args...]");
print(" init Fetch default sources");
print(" install [source/]package-name Install by name");
print(" install uuid:[source/]package-uuid Install by uuid");
print(" list [source] List packages");
print(" search <name | '*'> [tags...] Search packages");
print(" sync Sync all sources");
print(" usage This");
}
Expand All @@ -538,7 +686,10 @@ fn main(argv: [str]) {
}
let c = configure();
alt argv[1] {
"init" { cmd_init(c); }
"install" { cmd_install(c, argv); }
"list" { cmd_list(c, argv); }
"search" { cmd_search(c, argv); }
"sync" { cmd_sync(c, argv); }
"usage" { cmd_usage(); }
_ { cmd_usage(); }
Expand Down
103 changes: 103 additions & 0 deletions src/cargo/pgp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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;
}
3 changes: 3 additions & 0 deletions src/cargo/sources.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down