~apreiml/hare-openpgp

b2de9be7fb121c241dc6f875d19d0fbc6b8a641d — Armin Preiml 3 months ago 22bae58
key parse iterator and fleshing out stuff
M cmd/okp/main.ha => cmd/okp/main.ha +2 -2
@@ 177,7 177,7 @@ fn loadkey(path: str) openpgp::key = {
	//defer close_file(&f);

	// TODO ascii armor
	match (openpgp::parse_pubkey(&f)) {
	match (openpgp::parse_key(&f)) {
	case let p: openpgp::key =>
		return p;
	case let e: openpgp::error =>


@@ 247,7 247,7 @@ fn load_privkey(path: str) openpgp::key = {
	//defer close_file(&f);

	// TODO ascii armor
	match (openpgp::parse_privkey(&f)) {
	match (openpgp::parse_key(&f)) {
	case let p: openpgp::key =>
		return p;
	case let e: openpgp::error =>

M format/openpgp/errors.ha => format/openpgp/errors.ha +15 -3
@@ 1,11 1,13 @@
use io;


export type unsupported = !void;
export type unsupported = !str;
export type badsig = !void;
export type invalid = !void;
export type unexpected = !void;
export type weakhash = !void;

export type error = !(io::error | unsupported | badsig | invalid);
export type error = !(io::error | unsupported | badsig | invalid | unexpected | weakhash);

export fn strerror(e: error) str = {
	match (e) {


@@ 14,8 16,18 @@ export fn strerror(e: error) str = {
	case badsig =>
		return "Signature verification failed";
	case invalid =>
		return "Invalid";
		return "Invalid data format";
	case unexpected =>
		return "Unexpected data";
	case weakhash =>
		return "Signature on key or data is done using a weak hash function and can not be trusted.";
	case let e: io::error =>
		return io::strerror(e);
	};
};

let unsupported_version: unsupported = "Unsupported file version";
let unsupported_encalgo: unsupported = "Unsupported encryption algorithm";
let unsupported_hashalgo: unsupported = "Unsupported hash algorithm";
let unsupported_fp: unsupported = "Unsupported fingerprint method";
let unsupported_critsub: unsupported = "Unsupported critical signature sub-packet";

M format/openpgp/key.ha => format/openpgp/key.ha +3 -195
@@ 47,7 47,7 @@ export type key = struct {

export type subkey = struct {
	key: *keypair,
	// TODO signature
	// TODO signatures
	// TODO revocation
};



@@ 101,7 101,7 @@ fn parse_s2k(src: io::handle) (s2k | error) = {
	};
	if (s.s2ktype != s2ktype::SIMPLE && s.s2ktype != s2ktype::SALTED
			&& s.s2ktype != s2ktype::ITERATED_AND_SALTED) {
		return unsupported;
		abort("todo");
	};

	if (s.s2ktype == s2ktype::SALTED


@@ 127,11 127,10 @@ fn ivsz(algo: u8) (size | error) = {
	case sencalgo::AES256 =>
		return 32z;
	case =>
		return unsupported;
		return unsupported_encalgo;
	};
};


fn keypair_verify(
	k: *keypair,
	sig: io::handle,


@@ 159,194 158,3 @@ fn keypair_finish(k: *keypair) void = {
	k.vtable.finish(k);
};

export type rsakey = struct {
	key: keypair,
	pub: []u8,
	priv: []u8,
};

export fn newrsakey(created: date::date) rsakey = rsakey {
	key = keypair {
		vtable = &rsakey_vtable,
		algo = 1,
		priv = false,
		created = created,
		fp = [0...],
		s2k = null,
	},
	...
};

const rsakey_vtable = keypair_vtable {
	read_pub = &rsa_read_pub,
	read_priv = &rsa_read_priv,
	sign = &rsa_sign,
	verify = &rsa_verify,
	finish = &rsa_finish,
	keygrip = &rsa_keygrip,
};

fn rsa_read_pub(k: *keypair, src: io::handle) (void | error) = {
	let k = k: *rsakey;
	let pub = rsa::pubparams { ... };

	pub.n = readmpi(src)?;
	defer free(pub.n);
	pub.e = readmpi(src)?;
	defer free(pub.e);

	assert(len(k.pub) == 0, "can not reassign keys");
	k.pub = alloc([0...], rsa::PUBKEYSZ);
	match (rsa::pubkey_init(k.pub, pub)) {
	case size =>
		yield;
	case rsa::error =>
		free(k.pub);
		return errors::invalid;
	};
};

fn rsa_read_priv(k: *keypair, src: io::handle) (void | error) = {
	let k = k: *rsakey;

	let priv = rsa::privparams { ... };
	let d = readmpi(src)?;
	defer {
		bytes::zero(d);
		free(d);
	};
	priv.q = readmpi(src)?;
	defer {
		bytes::zero(priv.q);
		free(priv.q);
	};
	priv.p = readmpi(src)?;
	defer {
		bytes::zero(priv.p);
		free(priv.p);
	};
	priv.iq = readmpi(src)?;
	defer {
		bytes::zero(priv.iq);
		free(priv.iq);
	};

	let check: [2]u8 = [0...];
	readall(src, check)?;
	bytes::zero(check);

	let pub = rsa::pubkey_params(k.pub);

	k.priv = alloc([0...], rsa::PRIVKEYSZ);
	match (rsa::privkey_initd(k.priv, priv, d, pub.n)) {
	case size =>
		yield;
	case rsa::error =>
		bytes::zero(k.priv);
		free(k.priv);
		return errors::invalid;
	};
};

fn rsa_verify(
	k: *keypair,
	sig: io::handle,
	hashsum: []u8,
	hashalgo: u8,
) (void | error) = {
	let k = k: *rsakey;
	let buf: [rsa::PKCS1_VERIFYBUFSZ]u8 = [0...];
	let sig = io::drain(sig)?;
	defer free(sig);

	// first two bytes contain the length of the signature in bits
	const nsize = endian::begetu16(sig[..2]);
	if (nsize > rsa::pubkey_nbitlen(k.pub)) {
		return badsig;
	};

	let sig = sig[2..];
	const algo = rsa_hashalgo(hashalgo)?;

	let r = rsa::pkcs1_verify(k.pub, hashsum, sig, algo, buf);
	if (r is rsa::error) {
		return badsig;
	};
};

fn rsa_hashalgo(ha: u8) (rsa::pkcs1_hashalgo | error) = {
	switch (ha) {
	case hashalgo::SHA256 =>
		return rsa::pkcs1_hashalgo::SHA256;
	case hashalgo::SHA512 =>
		return rsa::pkcs1_hashalgo::SHA512;
	case =>
		return unsupported;
	};
};

fn rsa_sign(
	k: *keypair,
	hashsum: []u8,
	hashalgo: u8,
	sig: io::handle,
) (size | error) = {
	let k = k: *rsakey;
	let buf: [rsa::PKCS1_SIGNBUFSZ]u8 = [0...];

	const nsize = rsa::privkey_nsize(k.priv);
	let sigbuf: []u8 = alloc([0...], nsize);
	defer free(sigbuf);

	const algo = rsa_hashalgo(hashalgo)?;

	match (rsa::pkcs1_sign(k.priv, hashsum, sigbuf, algo, buf)) {
	case rsa::error =>
		return badsig;
	case void =>
		yield;
	};

	// Natural occuring computational errors during the signature generation
	// can lead to a complete compromise. Therefore to work around this
	// issue the signature will be verified before writing it to 'sink'.
	//
	// See "Passive SSH Key Compromise via Lattices"
	// https://eprint.iacr.org/2023/1711
	match (rsa::pkcs1_verify(k.pub, hashsum, sigbuf, algo, buf)) {
	case rsa::error =>
		return badsig;
	case void =>
		yield;
	};

	const nbitlen = rsa::privkey_nbitlen(k.priv);
	let n = io::writeall(sig, [(nbitlen >> 8): u8, nbitlen: u8])?;
	n += io::writeall(sig, sigbuf)?;
	return n;
};

fn rsa_finish(k: *keypair) void = {
	let k = k: *rsakey;
	bytes::zero(k.priv);
	free(k.priv);
	free(k.pub);
};

// TODO pass kg by reference to write to?
fn rsa_keygrip(k: *keypair) [KEYGRIPSZ]u8 = {
	let kg: [KEYGRIPSZ]u8 = [0...];

	let params = rsa::pubkey_params((k: *rsakey).pub);
	let n = params.n;
	let i = 0z;
	for (i < len(n) && n[i] == 0; i += 1) continue;

	let h = sha1::sha1();
	if (n[i] & 0x80 == 0x80) {
		io::write(&h, [0u8])!;
	};
	io::write(&h, n[i..])!;
	hash::sum(&h, kg);
	return kg;
};

M format/openpgp/multihash.ha => format/openpgp/multihash.ha +51 -2
@@ 1,17 1,23 @@
use crypto::sha1;
use crypto::sha256;
use crypto::sha512;
use hash;
use io;

def MAX_HASHSZ = sha512::SZ;

const multihash_vt = io::vtable {
	writer = &multihash_writer,
	closer = &multihash_closer,
	...
};

type multihash = struct {
	hash::hash,
	sha1: sha1::state,
	sha256: sha256::state,
	sha512: sha512::digest,
	selected: (void | hashalgo),
};

fn newmultihash() multihash = multihash {


@@ 20,22 26,65 @@ fn newmultihash() multihash = multihash {
	reset = null,
	sz = 0,
	bsz = 0,
	sha1 = sha1::sha1(),
	sha256 = sha256::sha256(),
	sha512 = sha512::sha512(),
	selected = void,
};

fn multihash_writer(s: *io::stream, buf: const []u8) (size | io::error) = {
	let mh = s: *multihash;
	io::write(&mh.sha1, buf)!;
	io::write(&mh.sha256, buf)!;
	io::write(&mh.sha512, buf)!;
	return len(buf);
};

fn multihash_select(h: *multihash, algo: u8) (void | unsupported) = {
	switch (algo) {
	case hashalgo::SHA1, hashalgo::SHA256, hashalgo::SHA512 =>
		yield;
	case =>
		return unsupported_hashalgo;
	};

	h.selected = algo: hashalgo;
	let sh = multihash_selected(h);
	h.sz = hash::sz(sh);
	h.bsz = hash::bsz(sh);
};

fn multihash_selected(h: *multihash) *hash::hash = {
	match (h.selected) {
	case void =>
		abort("hash not selected");
	case let a: hashalgo =>
		switch (a) {
		case hashalgo::SHA1 =>
			return &h.sha1;
		case hashalgo::SHA256 =>
			return &h.sha256;
		case hashalgo::SHA512 =>
			return &h.sha512;
		case =>
			abort("unsupported in select");
		};
	};
};

fn multihash_sum(h: *hash::hash, buf: []u8) void = {
	let mh = h: *multihash;
	hash::sum(&mh.sha256, buf);
	let h = h: *multihash;
	let hs = multihash_selected(h);
	hash::sum(hs, buf);
};

fn multihash_clone(mh: *multihash) multihash = {
	return *mh;
};

fn multihash_closer(h: *io::stream) (void | io::error) = {
	let h = h: *multihash;
	hash::close(&h.sha1);
	hash::close(&h.sha256);
	hash::close(&h.sha512);
};

M format/openpgp/reader.ha => format/openpgp/reader.ha +68 -31
@@ 15,9 15,9 @@ use time::chrono;
use time::date;
use time;

// use debug::*;
use debug::*;

type reader = struct {
export type reader = struct {
	h: io::handle,
	next: (void | packet),
};


@@ 80,7 80,9 @@ fn next(d: *reader) (packet | error) = {
};

fn hasnext(d: *reader) (bool | error) = {
	print("hasnext");
	if (d.next is packet) {
		print("cached");
		return true;
	};



@@ 305,19 307,38 @@ fn pkt_parseuserid(p: *packet, mh: *multihash) (str | error) = {
	};
};

export fn parse_pubkey(h: io::handle) (key | error) = {
	return parse_key(h, false);
export type keyiter = struct {
	r: reader,
};

export fn parse_privkey(h: io::handle) (key | error) = {
	return parse_key(h, true);
export fn load_keys(h: io::handle) keyiter = keyiter {
	r = newreader(h),
};

fn parse_key(h: io::handle, priv: bool) (key | error) = {
export fn parse_key(h: io::handle) (key | void | error) = {
	let iter = load_keys(h);
	return keys_next(&iter);
};

export fn keys_next(iter: *keyiter) (key | void | error) = {
	print("Next");
	let mh = newmultihash();
	let d = newreader(h);
	let g = &iter.r;

	let p = next(&d)?;
	if (!hasnext(g)?) {
		return void;
	};
	let p = next(g)?;
	print(p.tag: u8);

	const priv = switch (p.tag) {
	case tag::SECRET_KEY =>
		yield true;
	case tag::PUBLIC_KEY =>
		yield false;
	case =>
		return unexpected;
	};

	let k = key {
		primary = pkt_parse_key(&p, &mh, priv)?,


@@ 328,29 349,41 @@ fn parse_key(h: io::handle, priv: bool) (key | error) = {
	let valid = false;
	defer if (!valid) key_finish(&k);

	for (hasnext(&d)? && peek(&d) == tag::SIGNATURE) {
		p = next(&d)?;
	for (hasnext(g)? && peek(g) == tag::SIGNATURE) {
		p = next(g)?;
		// TODO Zero or more revocation signatures
		//println("TODO: REVOCATION SIGNATURE")!;
		pkt_dump(&p);
	};

	if (!hasnext(&d)? || peek(&d) != tag::USER_ID) {
	if (!hasnext(g)? || peek(g) != tag::USER_ID) {
		return invalid;
	};

	for (hasnext(&d)? && peek(&d) == tag::USER_ID) {
	print("parse user ids");
	for (hasnext(g)? && peek(g) == tag::USER_ID) {
		print(" user id");
		let mh = multihash_clone(&mh);
		p = next(&d)?;
		p = next(g)?;
		let id = pkt_parseuserid(&p, &mh)?;
		append(k.userids, userid { id = id });

		if (hasnext(&d)? && peek(&d) == tag::SIGNATURE) {
			p = next(&d)?;
		for (hasnext(g)? && peek(g) == tag::SIGNATURE) {
			let mh = multihash_clone(&mh);
			print(" sig");
			p = next(g)?;
			let sig = pkg_parsesig(&p)?;
			defer sig_free(&sig);
			verify(&sig, &mh, &k)?;
			multihash_select(&mh, sig.hashalgo)?;
			// TODO figure out self signed signatures, verify
			// and mark them
			//verify(&sig, &mh, &k)?;
			//verified
			// TODO parse unsupported signatures and mark them
			//      as not verified, or implement DSA?
		};
		// TODO Sig type - Positive certification of a User ID and Public Key packet(0x13).
		// TODO Sig type - Generic certification of a User ID and Public Key packet(0x10).
	};

	// TODO primary key attributes: 


@@ 382,12 415,13 @@ fn parse_key(h: io::handle, priv: bool) (key | error) = {
	//   contain an issuer subpacket for each key, as a way of explicitly
	//   tying those keys to the signature.

	print("parse user attributes");
	for (hasnext(g)? && peek(g) == tag::USER_ATTRIBUTE) {
		print(" has attr");
		p = next(g)?;

	for (hasnext(&d)? && peek(&d) == tag::USER_ATTRIBUTE) {
		p = next(&d)?;

		if (hasnext(&d)? && peek(&d) == tag::SIGNATURE) {
			p = next(&d)?;
		if (hasnext(g)? && peek(g) == tag::SIGNATURE) {
			p = next(g)?;
		};
	};



@@ 404,30 438,33 @@ fn parse_key(h: io::handle, priv: bool) (key | error) = {
	// used only for certifying subkeys that are used for encryption and
	// signatures.

	print("parse sub keys");
	const subkeytag = if (priv) tag::SECRET_SUBKEY else tag::PUBLIC_SUBKEY;
	for (hasnext(&d)? && peek(&d) == subkeytag) {
	for (hasnext(g)? && peek(g) == subkeytag) {
		print(" - subkey");
		let mh = multihash_clone(&mh);
		p = next(&d)?;
		p = next(g)?;
		let sk = pkt_parse_key(&p, &mh, priv)?;
		append(k.sub, subkey { key = sk });


		p = next(&d)?;
		p = next(g)?;

		let sig = pkg_parsesig(&p)?;
		multihash_select(&mh, sig.hashalgo)?;
		defer sig_free(&sig);
		if (sig.sigtype != sigtype::SUBKEY_BINDING_SIG) {
			abort("unexpected subkey sig");
		};
		verify(&sig, &mh, &k)?;

		if (hasnext(&d)? && peek(&d) == tag::SIGNATURE) {
			p = next(&d)?;
		if (hasnext(g)? && peek(g) == tag::SIGNATURE) {
			p = next(g)?;
			pkt_dump(&p);
		};
	};

	if (hasnext(&d)?) {
		return invalid;
	};

	print("done, next is", (g.next: packet).tag: u8);
	valid = true;
	return k;
};

A format/openpgp/rsa.ha => format/openpgp/rsa.ha +205 -0
@@ 0,0 1,205 @@
use bytes;
use crypto::rsa;
use crypto::sha1;
use crypto::sha256;
use crypto::sha512;
use time::date;
use endian;
use errors;
use hash;
use io;

export type rsakey = struct {
	key: keypair,
	pub: []u8,
	priv: []u8,
};

fn newrsakey(created: date::date) rsakey = rsakey {
	key = keypair {
		vtable = &rsakey_vtable,
		algo = 1,
		priv = false,
		created = created,
		fp = [0...],
		s2k = null,
	},
	...
};

const rsakey_vtable = keypair_vtable {
	read_pub = &rsa_read_pub,
	read_priv = &rsa_read_priv,
	sign = &rsa_sign,
	verify = &rsa_verify,
	finish = &rsa_finish,
	keygrip = &rsa_keygrip,
};

fn rsa_read_pub(k: *keypair, src: io::handle) (void | error) = {
	let k = k: *rsakey;
	let pub = rsa::pubparams { ... };

	pub.n = readmpi(src)?;
	defer free(pub.n);
	pub.e = readmpi(src)?;
	defer free(pub.e);

	assert(len(k.pub) == 0, "can not reassign keys");
	k.pub = alloc([0...], rsa::PUBKEYSZ);
	match (rsa::pubkey_init(k.pub, pub)) {
	case size =>
		yield;
	case rsa::error =>
		free(k.pub);
		return errors::invalid;
	};
};

fn rsa_read_priv(k: *keypair, src: io::handle) (void | error) = {
	let k = k: *rsakey;

	let priv = rsa::privparams { ... };
	let d = readmpi(src)?;
	defer {
		bytes::zero(d);
		free(d);
	};
	priv.q = readmpi(src)?;
	defer {
		bytes::zero(priv.q);
		free(priv.q);
	};
	priv.p = readmpi(src)?;
	defer {
		bytes::zero(priv.p);
		free(priv.p);
	};
	priv.iq = readmpi(src)?;
	defer {
		bytes::zero(priv.iq);
		free(priv.iq);
	};

	let check: [2]u8 = [0...];
	readall(src, check)?;
	bytes::zero(check);

	let pub = rsa::pubkey_params(k.pub);

	k.priv = alloc([0...], rsa::PRIVKEYSZ);
	match (rsa::privkey_initd(k.priv, priv, d, pub.n)) {
	case size =>
		yield;
	case rsa::error =>
		bytes::zero(k.priv);
		free(k.priv);
		return errors::invalid;
	};
};

fn rsa_verify(
	k: *keypair,
	sig: io::handle,
	hashsum: []u8,
	hashalgo: u8,
) (void | error) = {
	let k = k: *rsakey;
	let buf: [rsa::PKCS1_VERIFYBUFSZ]u8 = [0...];
	let sig = io::drain(sig)?;
	defer free(sig);

	// first two bytes contain the length of the signature in bits
	const nsize = endian::begetu16(sig[..2]);
	if (nsize > rsa::pubkey_nbitlen(k.pub)) {
		return badsig;
	};

	let sig = sig[2..];
	const algo = rsa_hashalgo(hashalgo)?;

	let r = rsa::pkcs1_verify(k.pub, hashsum, sig, algo, buf);
	if (r is rsa::error) {
		return badsig;
	};
};

fn rsa_hashalgo(ha: u8) (rsa::pkcs1_hashalgo | error) = {
	switch (ha) {
	case hashalgo::SHA1 =>
		return rsa::pkcs1_hashalgo::SHA1;
	case hashalgo::SHA256 =>
		return rsa::pkcs1_hashalgo::SHA256;
	case hashalgo::SHA512 =>
		return rsa::pkcs1_hashalgo::SHA512;
	case =>
		return unsupported_hashalgo;
	};
};

fn rsa_sign(
	k: *keypair,
	hashsum: []u8,
	hashalgo: u8,
	sig: io::handle,
) (size | error) = {
	let k = k: *rsakey;
	let buf: [rsa::PKCS1_SIGNBUFSZ]u8 = [0...];

	const nsize = rsa::privkey_nsize(k.priv);
	let sigbuf: []u8 = alloc([0...], nsize);
	defer free(sigbuf);

	const algo = rsa_hashalgo(hashalgo)?;

	match (rsa::pkcs1_sign(k.priv, hashsum, sigbuf, algo, buf)) {
	case rsa::error =>
		return badsig;
	case void =>
		yield;
	};

	// Natural occuring computational errors during the signature generation
	// can lead to a complete compromise. Therefore to work around this
	// issue the signature will be verified before writing it to 'sink'.
	//
	// See "Passive SSH Key Compromise via Lattices"
	// https://eprint.iacr.org/2023/1711
	match (rsa::pkcs1_verify(k.pub, hashsum, sigbuf, algo, buf)) {
	case rsa::error =>
		return badsig;
	case void =>
		yield;
	};

	const nbitlen = rsa::privkey_nbitlen(k.priv);
	let n = io::writeall(sig, [(nbitlen >> 8): u8, nbitlen: u8])?;
	n += io::writeall(sig, sigbuf)?;
	return n;
};

fn rsa_finish(k: *keypair) void = {
	let k = k: *rsakey;
	bytes::zero(k.priv);
	free(k.priv);
	free(k.pub);
};

// TODO pass kg by reference to write to?
fn rsa_keygrip(k: *keypair) [KEYGRIPSZ]u8 = {
	let kg: [KEYGRIPSZ]u8 = [0...];

	let params = rsa::pubkey_params((k: *rsakey).pub);
	let n = params.n;
	let i = 0z;
	for (i < len(n) && n[i] == 0; i += 1) continue;

	let h = sha1::sha1();
	if (n[i] & 0x80 == 0x80) {
		io::write(&h, [0u8])!;
	};
	io::write(&h, n[i..])!;
	hash::sum(&h, kg);
	return kg;
};


M format/openpgp/sig.ha => format/openpgp/sig.ha +166 -11
@@ 18,27 18,177 @@ use time;
use types;

use debug::*;
use crypto::rsa;

export type sigtype = enum u8 {
	// Signature of a binary document
	BINARY = 0x00,

	// Signature of a canonical text document, where line endings have been
	// converted to <CR><LF>
	CANONICAL_TEXT = 0x01,

	// A signature of its own subpacket contents.
	STANDALONE = 0x02,

	// The issuer of this certification does not make any particular
	// assertion as to how well the certifier has checked that the owner
	// of the key is in fact the person described by the User ID.
	GENERIC_CERT_OF_USERID_AND_PUBKEY = 0x10,

	// The issuer of this certification has not done any verification of
	// the claim that the owner of this key is the User ID specified.
	PERSONA_CERT_OF_USERID_AND_PUBKEY = 0x11,

	// The issuer of this certification has done some casual
	// verification of the claim of identity.
	CASUAL_CERT_OF_USERID_AND_PUBKEY = 0x12,

	// The issuer of this certification has done substantial
	// verification of the claim of identity.
	POSITIVE_CERT_OF_USERID_AND_PUBKEY = 0x13,

	// This signature is a statement by the top-level signing key that
	// indicates that it owns the subkey.
	SUBKEY_BINDING_SIG = 0x18,

	// This signature is a statement by a signing subkey, indicating
	// that it is owned by the primary key and subkey.
	PRIMARY_BINDING_SIG = 0x19,

	// This signature is calculated directly on a key.
	DIRECT_KEY_SIG = 0x1f,

	// The signature is calculated directly on the key being revoked.
	REVOCATION_SIG = 0x20,

	// The signature is calculated directly on the subkey being revoked.
	SUBKEY_REVOCATION_SIG = 0x28,

	// This signature revokes an earlier User ID certification signature
	// (signature class 0x10 through 0x13) or direct-key signature (0x1F).
	CERT_REVOCATION_SIG = 0x30,

	TIMESTAMP_SIG = 0x40,

	THIRD_PARTY_CONFIRM_SIG = 0x50,
};

export fn strsigtype(st: u8) str = {
	switch (st) {
	case sigtype::BINARY =>
		return "BINARY";
	case sigtype::CANONICAL_TEXT =>
		return "CANONICAL_TEXT";
	case sigtype::STANDALONE =>
		return "STANDALONE";
	case sigtype::GENERIC_CERT_OF_USERID_AND_PUBKEY =>
		return "GENERIC_CERT_OF_USERID_AND_PUBKEY";
	case sigtype::PERSONA_CERT_OF_USERID_AND_PUBKEY =>
		return "PERSONA_CERT_OF_USERID_AND_PUBKEY";
	case sigtype::CASUAL_CERT_OF_USERID_AND_PUBKEY =>
		return "CASUAL_CERT_OF_USERID_AND_PUBKEY";
	case sigtype::POSITIVE_CERT_OF_USERID_AND_PUBKEY =>
		return "POSITIVE_CERT_OF_USERID_AND_PUBKEY";
	case sigtype::SUBKEY_BINDING_SIG =>
		return "SUBKEY_BINDING_SIG";
	case sigtype::PRIMARY_BINDING_SIG =>
		return "PRIMARY_BINDING_SIG";
	case sigtype::DIRECT_KEY_SIG =>
		return "DIRECT_KEY_SIG";
	case sigtype::REVOCATION_SIG =>
		return "REVOCATION_SIG";
	case sigtype::SUBKEY_REVOCATION_SIG =>
		return "SUBKEY_REVOCATION_SIG";
	case sigtype::CERT_REVOCATION_SIG =>
		return "CERT_REVOCATION_SIG";
	case sigtype::TIMESTAMP_SIG =>
		return "TIMESTAMP_SIG";
	case sigtype::THIRD_PARTY_CONFIRM_SIG =>
		return "THIRD_PARTY_CONFIRM_SIG";
	case =>
		return "UNKNOWN";
	};
};

export type subtype = enum u8 {
	SIG_CREATION_TIME = 2,
	SIG_EXPIRATION_TIME = 3,

	EXPORTABLE_CERT = 4,
	TRUST_SIG = 5,
	REG_EXP = 6,
	REVOCABLE = 7,
	KEY_EXPIRATION_TIME = 8,
	PREFERRED_SYM_ALGOS = 11,
	REVOCATION_KEY = 12,
	ISSUER = 16,
	NOTATION_DATA = 20,
	PREFERRED_HASH_ALGOS = 21,
	PREFERRED_COMPRESSION_ALGOS = 22,

	KEY_SERVER_PREFERENCES = 24,
	PRIMARY_USERID = 25,
	POLICY_URI = 26,
	KEYFLAGS = 27,

	SIGNER_USERID = 28,
	REVOCATION_REASON = 29,
	FEATURES = 30,
	SIG_TARGET = 31,
	EMBEDDED_SIG = 32,
	ISSUER_FINGERPRINT = 33,
};

export fn strsubtype(st: u8) str = {
	switch (st) {
	case subtype::SIG_CREATION_TIME =>
		return "SIG_CREATION_TIME";
	case subtype::SIG_EXPIRATION_TIME =>
		return "SIG_EXPIRATION_TIME";
	case subtype::EXPORTABLE_CERT =>
		return "EXPORTABLE_CERT";
	case subtype::TRUST_SIG =>
		return "TRUST_SIG";
	case subtype::REG_EXP =>
		return "REG_EXP";
	case subtype::REVOCABLE =>
		return "REVOCABLE";
	case subtype::KEY_EXPIRATION_TIME =>
		return "KEY_EXPIRATION_TIME";
	case subtype::PREFERRED_SYM_ALGOS =>
		return "PREFERRED_SYM_ALGOS";
	case subtype::REVOCATION_KEY =>
		return "REVOCATION_KEY";
	case subtype::ISSUER =>
		return "ISSUER";
	case subtype::NOTATION_DATA =>
		return "NOTATION_DATA";
	case subtype::PREFERRED_HASH_ALGOS =>
		return "PREFERRED_HASH_ALGOS";
	case subtype::PREFERRED_COMPRESSION_ALGOS =>
		return "PREFERRED_COMPRESSION_ALGOS";
	case subtype::KEY_SERVER_PREFERENCES =>
		return "KEY_SERVER_PREFERENCES";
	case subtype::PRIMARY_USERID =>
		return "PRIMARY_USERID";
	case subtype::POLICY_URI =>
		return "POLICY_URI";
	case subtype::KEYFLAGS =>
		return "KEYFLAGS";
	case subtype::SIGNER_USERID =>
		return "SIGNER_USERID";
	case subtype::REVOCATION_REASON =>
		return "REVOCATION_REASON";
	case subtype::FEATURES =>
		return "FEATURES";
	case subtype::SIG_TARGET =>
		return "SIG_TARGET";
	case subtype::EMBEDDED_SIG =>
		return "EMBEDDED_SIG";
	case subtype::ISSUER_FINGERPRINT =>
		return "ISSUER_FINGERPRINT";
	case =>
		return "UNKNOWN";
	};
};

export type keyflag = enum u8 {
	// The key may be used to certify other keys.
	CERTIFY = 0x01,


@@ 99,10 249,11 @@ fn pkg_parsesig(p: *packet) (sig | error) = {
	};
	s.v = readu8(p)?;
	if (s.v != 4) {
		return errors::unsupported;
		return unsupported_version;
	};

	s.sigtype = readu8(p)?;
	print("SIGTYPE: ", strsigtype(s.sigtype));
	s.pubkeyalgo = readu8(p)?;
	s.hashalgo = readu8(p)?;



@@ 139,11 290,11 @@ fn pkg_parsesig(p: *packet) (sig | error) = {
			n += 1;
		case subtype::ISSUER_FINGERPRINT =>
			const fpv = readu8(&sub)?;
			if (fpv != 4 || subsz != 1 + FPSZ) return unsupported;
			if (fpv != 4 || subsz != 1 + FPSZ) return unsupported_fp;
			s.opts.keyfp = io::drain(&sub)?;
			n += 1 + len(s.opts.keyfp);
		case =>
			if (crit) return unsupported;
			if (crit) return unsupported_critsub;
			n += io::copy(io::empty, &sub)?;
		};



@@ 191,17 342,20 @@ fn parsesubsz(src: io::handle) (size | error | io::EOF) = {
// Hash must be freed and finished after use.
export fn sig_hashfn(s: *sig) (*hash::hash | unsupported) = {
	switch (s.hashalgo) {
	case hashalgo::SHA1 =>
		return alloc(sha1::sha1());
	case hashalgo::SHA256 =>
		return alloc(sha256::sha256());
	case hashalgo::SHA512 =>
		return alloc(sha512::sha512());
	case =>
		return unsupported;
		return unsupported_hashalgo;
	};
};

// TODO maybe verify_sig or just verify?
export fn verify(s: *sig, h: *hash::hash, k: *key) (void | error) = {
	print("verify");
	io::writeall(h, [s.v, s.sigtype, s.pubkeyalgo, s.hashalgo])!;

	if (len(s.hsub) > types::U16_MAX) {


@@ 221,10 375,12 @@ export fn verify(s: *sig, h: *hash::hash, k: *key) (void | error) = {
	endian::beputu32(szbuf, sz: u32);
	io::writeall(h, szbuf)!;

	let sum: [sha256::SZ]u8 = [0...];
	let sum: [MAX_HASHSZ]u8 = [0...];
	let sum = sum[..hash::sz(h)];
	hash::sum(h, sum);

	if (!bytes::equal(s.hprefix, sum[..2])) {
		print("invalid PREFIX", s.hashalgo: u8);
		return invalid;
	};



@@ 283,8 439,7 @@ export fn sign(dest: io::handle, key: *key, opts: signopts) (size | error) = {
	io::writeall(&w, s[..2])?;

	// TODO get enc key
	let k = key.primary: *rsakey;

	let k = key.primary;
	keypair_sign(k, s, hashalgo::SHA256, &w)?;

	return pkt_write(dest, tag::SIGNATURE, memio::buffer(&w))?;

M format/openpgp/testcases+test.ha => format/openpgp/testcases+test.ha +5 -5
@@ 12,7 12,7 @@ use debug::*;

@test fn parse_pub() void = {
	let bin = memio::fixed(samp_rsakey.pub);
	let pub = parse_pubkey(&bin)!;
	let pub = parse_key(&bin)! as key;
	defer key_finish(&pub);

	assert(bytes::equal(pub.primary.fp, [


@@ 26,7 26,7 @@ use debug::*;

@test fn parse_sig() void = {
	let bin = memio::fixed(samp_rsakey.pub);
	let pub = parse_pubkey(&bin)!;
	let pub = parse_key(&bin)! as key;
	defer key_finish(&pub);

	let binsig = memio::fixed(samp_extsig);


@@ 45,7 45,7 @@ use debug::*;

@test fn parse_priv() void = {
	let bin = memio::fixed(samp_unprotrsakey.priv);
	let priv = parse_privkey(&bin)!;
	let priv = parse_key(&bin)! as key;
	defer key_finish(&priv);

	assert(bytes::equal(priv.primary.fp, [


@@ 61,7 61,7 @@ use debug::*;
	let dest = memio::dynamic();

	let bin = memio::fixed(samp_unprotrsakey.priv);
	let priv = parse_privkey(&bin)!;
	let priv = parse_key(&bin)! as key;
	defer key_finish(&priv);

	let h = sha256::sha256();


@@ 90,7 90,7 @@ use debug::*;
	};

	let bin = memio::fixed(samp_rsakey.pub);
	let pub = parse_pubkey(&bin)!;
	let pub = parse_key(&bin)! as key;
	defer key_finish(&pub);
	verify(&sig, hf, &pub)!;
};

M format/openpgp/types.ha => format/openpgp/types.ha +4 -4
@@ 5,7 5,7 @@ use hash;
use io;
use time::date;

type sizetype = enum {
export type sizetype = enum {
	FIXED,
	INDETERMINATE,
	PARTIAL,


@@ 24,7 24,7 @@ fn strsizetype(s: sizetype) str = {
	};
};

type packet = struct {
export type packet = struct {
	stream: io::stream,
	h: io::handle,
	pos: size,


@@ 34,7 34,7 @@ type packet = struct {
	sz: size,
};

type tag = enum u8 {
export type tag = enum u8 {
	RESERVED = 0,
	PUBLIC_KEY_ENCRYPTED_SESSION_KEY = 1,
	SIGNATURE = 2,


@@ 144,7 144,7 @@ fn newhash(algo: u8) (hash | unsupported) = {
	case hashalgo::SHA512 =>
		h.sha512 = sha512::sha512();
	case =>
		return unsupported;
		return unsupported_hashalgo;
	};
	return h;
};