@@ 17,7 17,7 @@ check:
clean:
rm ssh ssh-agent sshkey sshsign
-cmd: ssh ssh-agent sshkey
+cmd: ssh ssh-agent sshkey sshsign
ssh: cmd/ssh/main.ha
hare build cmd/ssh/
@@ 28,6 28,8 @@ ssh-agent: cmd/ssh-agent/main.ha
sshkey: cmd/sshkey/main.ha
hare build cmd/sshkey/
+sshsign: cmd/sshsign/*.ha
+ hare build cmd/sshsign/
install:
mkdir -p $(DESTDIR)$(THIRDPARTYDIR)/net/ssh/agent
@@ 0,0 1,184 @@
+use bufio;
+use bytes;
+use crypto::sha256;
+use crypto::sha512;
+use encoding::hex;
+use encoding::pem;
+use fmt;
+use format::ssh;
+use fs;
+use getopt;
+use hash;
+use io;
+use os;
+use strings;
+
+const magic: str = "SSHSIG";
+
+export fn main() void = {
+ const cmd = getopt::parse(os::args,
+ "ssh signer\n\nSigns or verifies data read from stdin.",
+ ('s', "file", "Verify input using given signature file"),
+ ('n', "namespace", "namespace"),
+ ('p', "pubkey", "Check signing key against public key file"),
+ ('i', "privkey", "Private key file to sign input with"),
+ );
+ defer getopt::finish(&cmd);
+
+ let sig: str = "";
+ let namespace: str = "";
+ let pubkeyfile: str = "";
+ let privkeyfile: str = "";
+
+ for (let i = 0z; i < len(cmd.opts); i += 1) {
+ const opt = cmd.opts[i];
+ switch (opt.0) {
+ case 's' =>
+ sig = opt.1;
+ case 'n' =>
+ namespace = opt.1;
+ case 'p' =>
+ pubkeyfile = opt.1;
+ case 'i' =>
+ privkeyfile = opt.1;
+ };
+ };
+
+ if (sig != "") {
+ let f = os::open(sig)!;
+ match (verify(f, namespace, os::stdin)) {
+ case let e: error =>
+ fmt::fatalf(strerror(e));
+ case void =>
+ fmt::println("signature ok")!;
+ };
+
+ if (pubkeyfile != "") {
+ abort("todo");
+ };
+
+ return;
+ };
+
+
+ let pf = os::open(privkeyfile)!;
+ let privfile = ssh::decodesshprivate(pf)!;
+ let key = ssh::decodeprivate(&privfile)!;
+};
+
+type invalidnamespace = !void;
+
+type error = (ssh::error | invalidnamespace | io::error);
+
+fn strerror(e: error) str = {
+ match (e) {
+ case let e: ssh::error =>
+ return ssh::strerror(e);
+ case invalidnamespace =>
+ return "invalid namespace";
+ };
+};
+
+fn verify(sigin: io::handle, namespace: str, msg: io::handle) (void | error) = {
+ let sigf = decodesignature(sigin)?;
+ let kbuf = bufio::fixed(sigf.pubkey, io::mode::READ);
+ let k = ssh::decodepublic(&kbuf)?;
+
+ if (sigf.namespace != namespace) {
+ return invalidnamespace;
+ };
+
+ let s = makesigmessage(sigf.hashalgo, namespace, msg)!;
+
+ let sigbuf = bufio::fixed(sigf.sig, io::mode::READ);
+ ssh::verify(&sigbuf, k, s)?;
+};
+
+fn sign(sink: io::handle, namespace: str, msg: io::handle, key: *ssh::key) (void | error) = {
+ return;
+};
+
+type sigfile = struct {
+ pubkey: []u8,
+ namespace: str,
+ hashalgo: str,
+ sig: []u8,
+};
+
+fn decodesignature(in: io::handle) (sigfile | io::error | ssh::error) = {
+ const pem = pem::newdecoder(in);
+ defer pem::finish(&pem);
+ const dec = match (pem::next(&pem)?) {
+ case io::EOF =>
+ return ssh::invalid;
+ case let dec: (str, pem::pemdecoder) =>
+ if (dec.0 != "SSH SIGNATURE") {
+ return ssh::invalid;
+ };
+ yield dec.1;
+ };
+
+ let magicbuf: [6]u8 = [0...];
+ match (io::readall(&dec, magicbuf)?) {
+ case size => void;
+ case io::EOF =>
+ return ssh::invalid;
+ };
+ if (!bytes::equal(magicbuf, strings::toutf8(magic))) {
+ return ssh::invalid;
+ };
+
+ const sigversion = readu32(&dec)?;
+ assert(sigversion == 1, "unsupported signature file version");
+
+ let sig = sigfile { ... };
+ sig.pubkey = readslice(&dec)?;
+ sig.namespace = readstr(&dec)?;
+ readslice(&dec)?; // reserved
+ sig.hashalgo = readstr(&dec)?;
+ sig.sig = readslice(&dec)?;
+
+ return sig;
+};
+
+fn hash(in: io::handle, hashalgo: str) ([]u8 | io::error) = {
+ let h = switch (hashalgo) {
+ case "sha512" =>
+ yield &sha512::sha512(): *hash::hash;
+ case "sha256" =>
+ yield &sha256::sha256(): *hash::hash;
+ case =>
+ abort("unsupported hash algorithm");
+ };
+ defer hash::close(h);
+
+ io::copy(h, in)?;
+
+ let sum: []u8 = alloc([0...], hash::sz(h));
+ hash::sum(h, sum);
+
+ return sum;
+};
+
+fn makesigmessage(
+ hashalgo: str,
+ namespace: str,
+ src: io::handle
+) ([]u8 | io::error) = {
+ let buf = bufio::dynamic(io::mode::WRITE);
+
+ io::write(&buf, strings::toutf8(magic))?;
+ writestr(&buf, namespace)?;
+ writestr(&buf, "")?;
+ writestr(&buf, hashalgo)?;
+
+ let h = hash(src, hashalgo)!;
+ defer free(h);
+
+ writeslice(&buf, h)?;
+
+ let signature = bufio::dynamic(io::mode::WRITE);
+
+ return bufio::buffer(&buf);
+};
+
@@ 0,0 1,60 @@
+use endian;
+use io;
+use strings;
+use types;
+use format::ssh;
+
+fn readu32(src: io::handle) (u32 | ssh::error) = {
+ let buf: [4]u8 = [0...];
+ match (io::readall(src, buf)?) {
+ case size => void;
+ case io::EOF =>
+ return ssh::invalid;
+ };
+ return endian::begetu32(buf);
+};
+
+fn readstr(src: io::handle) (str | ssh::error) = {
+ match (strings::fromutf8(readslice(src)?)) {
+ case let s: str =>
+ return s;
+ case =>
+ return ssh::invalid;
+ };
+};
+
+fn readslice(src: io::handle) ([]u8 | ssh::error) = {
+ const length = readu32(src)?;
+ if (length == 0) {
+ return [];
+ };
+
+ let buf: []u8 = alloc([0...], length);
+ match (io::readall(src, buf)) {
+ case size => void;
+ case io::EOF =>
+ free(buf);
+ return ssh::invalid;
+ case let e: io::error =>
+ free(buf);
+ return e;
+ };
+
+ return buf;
+};
+
+fn writeu32(sink: io::handle, v: u32) (void | io::error) = {
+ let buf: [4]u8 = [0...];
+ endian::beputu32(buf, v);
+ io::writeall(sink, buf)?;
+};
+
+fn writeslice(sink: io::handle, sl: []u8) (void | io::error) = {
+ assert(len(sl) <= types::U32_MAX, "parameter exceeds maximum length");
+ writeu32(sink, len(sl): u32)?;
+ io::writeall(sink, sl)?;
+};
+
+fn writestr(sink: io::handle, s: str) (void | io::error) = {
+ return writeslice(sink, strings::toutf8(s));
+};