commit 71971bb11a415d91306826d5a0c83e4eabade083
parent 93dc63b9b7373ca3038901fdeef4785332bdc8e4
Author: Armin Preiml <apreiml@strohwolke.at>
Date: Sun, 11 Aug 2024 19:15:21 +0200
crypto::rsa: add pss signature support
Signed-off-by: Armin Preiml <apreiml@strohwolke.at>
Diffstat:
2 files changed, 336 insertions(+), 0 deletions(-)
diff --git a/crypto/rsa/+test/pss_test.ha b/crypto/rsa/+test/pss_test.ha
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use bytes;
+use crypto::sha256;
+use hash;
+use memio;
+
+// Tests siging and verifying of a message against a signature that was
+// generated using golang 1.22.
+@test fn pss_sig() void = {
+ let salt: [_]u8 = [
+ 0x3e, 0xe9, 0x4c, 0x10, 0x61, 0x1d, 0x71, 0xe4, 0x8a, 0x49,
+ 0xb3, 0xd1, 0x56, 0xb5, 0x32, 0x52, 0x95, 0x1a, 0xad, 0xa6,
+ 0x40, 0xe2, 0xb7, 0xed, 0x05, 0x23, 0x87, 0xa7, 0xaf, 0x3f,
+ 0x9d, 0x1c, 0x70, 0x9d, 0x74, 0x75, 0x9c, 0x70, 0x29, 0x3a,
+ 0x55, 0x18, 0xf5, 0x35, 0xeb, 0x9a, 0x83, 0x39, 0xe0, 0x67,
+ 0x4f, 0xd8, 0x84, 0x5b, 0xcf, 0x02, 0xcf, 0xc0, 0x70, 0xcb,
+ 0xa1, 0x45, 0x0e, 0xd9, 0x76, 0xa9, 0x48, 0x8a, 0x06, 0x85,
+ 0x67, 0x67, 0x62, 0x3c, 0x31, 0x59, 0x81, 0x10, 0x06, 0x43,
+ 0x38, 0xd2, 0x2f, 0xe2, 0xfa, 0xc1, 0xc1, 0x8e, 0x3a, 0x0e,
+ 0x2c, 0x70, 0xec, 0x85, 0x28, 0xec, 0x43, 0xa1, 0x7b, 0xe2,
+ 0x75, 0xac, 0x0a, 0xe6, 0x59, 0x7d, 0x48, 0x04, 0x9d, 0x2b,
+ 0x4f, 0xb6, 0x32, 0x5a, 0x50, 0xda, 0x8b, 0xce, 0x93, 0xe7,
+ 0xed, 0xac, 0x51, 0x30, 0xb9, 0x01, 0xb3, 0x2e, 0x88, 0x34,
+ 0xc9, 0x00, 0xf8, 0x3c, 0xff, 0x0d, 0x9c, 0xea, 0x43, 0x7e,
+ 0xcc, 0xbc, 0x64, 0xda, 0x72, 0x16, 0x19, 0x2d, 0xe2, 0xd5,
+ 0xe9, 0x29, 0xfe, 0x17, 0x6a, 0xbd, 0xdc, 0x10, 0x7b, 0xbf,
+ 0x17, 0x69, 0x7e, 0x6e, 0xeb, 0x8d, 0x9b, 0x54, 0x9f, 0x8d,
+ 0xf4, 0x80, 0x99, 0xfc, 0xd3, 0x00, 0x3e, 0xf0, 0x9e, 0x71,
+ 0xcd, 0x38, 0x49, 0x25, 0x7d, 0x1f, 0x32, 0xd5, 0x2f, 0x02,
+ 0x5d, 0xbb, 0x40, 0x39, 0x9d, 0x57, 0x98, 0xe2, 0xc9, 0x7d,
+ 0x5b, 0x38, 0xb4, 0xe1, 0x50, 0xdf, 0xe5, 0x0e, 0xc4, 0xa3,
+ 0x47, 0xcf, 0x0b, 0x81, 0x14, 0xf1, 0x86, 0xb5, 0x5b, 0xb1,
+ 0x53, 0xbe, 0xec, 0x2d, 0x62, 0xdd, 0xd8, 0xd6, 0x71, 0xb6,
+ 0x3b, 0x3c, 0xf2, 0xe6, 0xa1, 0x87, 0xe6, 0x84, 0x27, 0x8b,
+ 0xee, 0xe8, 0x9d, 0x85, 0x3b, 0xd7, 0x80, 0xde, 0x65, 0x51,
+ 0x03, 0xac, 0xe6, 0x70, 0x96, 0xd2, 0x78, 0xa8, 0xaa, 0xd9,
+ 0xb3, 0xc9, 0xd0, 0x3f, 0x7d, 0x13, 0x6a, 0xfd, 0x70, 0x17,
+ 0xc5, 0xcd, 0xdd, 0x65, 0xf6, 0xa0, 0xa9, 0xd3, 0xa8, 0x55,
+ 0xda, 0x02, 0x48, 0x56, 0x72, 0x4f, 0x9f, 0x8b, 0x81, 0x5c,
+ 0xe7, 0x4f, 0x27, 0xd4, 0x97, 0x3f, 0x75, 0xa0, 0x23, 0x19,
+ 0x9a, 0xb4, 0xa1, 0xdc, 0x83, 0x65, 0x5f, 0x0d, 0xc5, 0x14,
+ 0x1b, 0xdb, 0xc9, 0x5c, 0x68, 0x51, 0x27, 0x5f, 0x0c, 0xe4,
+ 0xb4, 0x42, 0x51, 0xd8, 0xe6, 0x54, 0x6a, 0x26, 0x82, 0xad,
+ 0x48, 0x94, 0xc9, 0x37, 0x19, 0x51, 0xe8, 0xd6, 0xf7, 0x9b,
+ 0x77, 0x6e, 0x6f, 0xed, 0x44, 0xc5, 0xf1, 0x6a, 0x19, 0x5a,
+ ];
+
+ let sig: [_]u8 = [
+ 0x6d, 0x24, 0x30, 0xc3, 0x77, 0x76, 0x09, 0x3b, 0xac, 0xd7,
+ 0x30, 0x88, 0xab, 0x72, 0xb5, 0xd2, 0xee, 0xab, 0x3d, 0x26,
+ 0xa3, 0x8b, 0xa5, 0x74, 0xf6, 0x6b, 0x7b, 0x57, 0xcd, 0xe8,
+ 0xe3, 0xe7, 0x17, 0xbe, 0xf9, 0x15, 0x18, 0x38, 0x34, 0x51,
+ 0x3f, 0x0e, 0x8b, 0x80, 0x0b, 0xeb, 0xf2, 0xf8, 0x18, 0x38,
+ 0x0c, 0x6b, 0xfe, 0x30, 0x31, 0x62, 0x17, 0xb5, 0xef, 0x81,
+ 0xfa, 0x3b, 0x34, 0x03, 0xa7, 0x9b, 0xd8, 0x7b, 0xd2, 0xf4,
+ 0xb6, 0x93, 0x4e, 0xeb, 0x36, 0x52, 0xa0, 0x07, 0x93, 0xec,
+ 0x26, 0x01, 0xde, 0x32, 0xc9, 0xa1, 0x7a, 0x37, 0x6f, 0x57,
+ 0x57, 0x69, 0x12, 0x75, 0xe3, 0x37, 0x97, 0x4f, 0x9a, 0x52,
+ 0xe9, 0xb9, 0xb9, 0x80, 0xcb, 0x21, 0xdb, 0x6b, 0x48, 0xf2,
+ 0xc2, 0xdf, 0x3c, 0x29, 0xce, 0x6a, 0xab, 0x69, 0x02, 0xe3,
+ 0x78, 0xb1, 0xe5, 0x50, 0xea, 0x35, 0x5e, 0xf7, 0x8e, 0x9e,
+ 0x9e, 0x41, 0x2e, 0xb0, 0x46, 0x29, 0x54, 0xa3, 0x96, 0x4f,
+ 0x20, 0x44, 0x42, 0x65, 0x5b, 0x47, 0x85, 0xd6, 0x1d, 0x36,
+ 0xd6, 0xc2, 0xdf, 0x4f, 0xec, 0x7b, 0x72, 0xde, 0x88, 0x67,
+ 0x27, 0x2e, 0x1d, 0xa1, 0x7d, 0x8e, 0x36, 0xef, 0x0f, 0x7e,
+ 0x0c, 0x57, 0xaa, 0x6f, 0xe5, 0x3b, 0x6c, 0xfc, 0x61, 0xee,
+ 0xee, 0x91, 0x12, 0xa7, 0xa8, 0xd4, 0xeb, 0x63, 0xfd, 0x06,
+ 0x9f, 0x29, 0xd8, 0xf8, 0xc6, 0xdd, 0xdc, 0xf4, 0x53, 0x31,
+ 0x4e, 0xb5, 0xeb, 0x70, 0x27, 0xdb, 0x9b, 0xaf, 0x2d, 0x73,
+ 0x44, 0xff, 0x00, 0xc4, 0x0a, 0x29, 0xef, 0x2b, 0x22, 0x3f,
+ 0x2c, 0x79, 0x5c, 0xe3, 0x53, 0x47, 0xb5, 0xfd, 0x21, 0xa7,
+ 0x6b, 0x7d, 0x1a, 0x04, 0x58, 0x47, 0x07, 0xf0, 0x5f, 0x97,
+ 0xea, 0xf7, 0xcc, 0xec, 0x4c, 0xcd, 0x1f, 0x0d, 0xb3, 0x84,
+ 0xfb, 0xbc, 0x18, 0xa0, 0x88, 0x24, 0x59, 0x11, 0x25, 0x54,
+ 0x19, 0x44, 0xc3, 0xa4, 0x71, 0x67, 0xa6, 0x4c, 0xed, 0x9b,
+ 0x54, 0x49, 0xb6, 0x88, 0x3e, 0x0d, 0x18, 0xf5, 0xa7, 0x2c,
+ 0xb9, 0xa4, 0xbe, 0xbf, 0x9a, 0x54, 0xb8, 0x66, 0x23, 0x27,
+ 0xa4, 0x26, 0xf6, 0xfb, 0x2f, 0xa3, 0x22, 0x74, 0x8f, 0xdc,
+ 0xc5, 0x73, 0x26, 0xcc, 0x09, 0x67, 0xa0, 0x6a, 0x06, 0xdf,
+ 0xa1, 0xe5, 0x58, 0xcd, 0xf0, 0x7b, 0xb0, 0x25, 0xda, 0xc0,
+ 0xb6, 0x83, 0x27, 0xaf, 0x5c, 0xa3, 0x47, 0xb6, 0x40, 0xd2,
+ 0x47, 0x36, 0xda, 0xbf, 0x26, 0xb1, 0x65, 0x63, 0x69, 0xb7,
+ 0xd5, 0x5d, 0x0d, 0x73, 0xfe, 0x51, 0x7b, 0x1a, 0x63, 0x53,
+ 0x94, 0xf1, 0xd1, 0x26, 0x17, 0x4e, 0xc8, 0x29, 0xbb, 0xb2,
+ 0x6a, 0xb8, 0x72, 0x0e, 0xb2, 0xf4, 0x49, 0x12, 0x47, 0x16,
+ 0x1a, 0xfd, 0xae, 0x7e, 0x16, 0x68, 0x0b, 0x97, 0x71, 0x52,
+ 0x49, 0x92, 0x62, 0x26,
+ ];
+
+ let digest: [sha256::SZ]u8 = [0...];
+ let hf = sha256::sha256();
+ hash::write(&hf, msg);
+ hash::sum(&hf, digest);
+
+ let pk = sign3072.priv;
+ let priv: [PRIVKEYSZ]u8 = [0...];
+ privkey_init(priv, sign3072.priv)!;
+
+ let randrd = memio::fixed(salt);
+ let newsig: [384]u8 = [0...];
+
+ let signbuf: [PSS_SIGNBUFSZ]u8 = [0...];
+ pss_sign(priv, digest, newsig, &hf, &randrd, signbuf)!;
+
+ assert(bytes::equal(newsig, sig));
+
+ let pub: [PUBKEYSZ]u8 = [0...];
+ pubkey_init(pub, sign3072.pub)!;
+
+ let verifybuf: [PSS_VERIFYBUFSZ]u8 = [0...];
+ match (pss_verify(pub, digest, sig, &hf, verifybuf)) {
+ case void => void;
+ case let e: error =>
+ abort(strerror(e));
+ };
+
+ sig[10] += 1;
+ match (pss_verify(pub, digest, sig, &hf, verifybuf)) {
+ case void =>
+ abort("should fail");
+ case let e: error => void;
+ };
+
+ sig[10] -= 1;
+ digest[10] += 1;
+ match (pss_verify(pub, digest, sig, &hf, verifybuf)) {
+ case void =>
+ abort("should fail");
+ case let e: error => void;
+ };
+};
diff --git a/crypto/rsa/pss.ha b/crypto/rsa/pss.ha
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use bytes;
+use crypto::math;
+use endian;
+use errors;
+use hash;
+use io;
+use types;
+
+export type default = void;
+
+// Required minimum buffer size for [[pss_verify]]
+export def PSS_VERIFYBUFSZ = PUBEXP_BUFSZ + ((BITSZ + 7) / 8);
+
+// Required minimum buffer size for [[pss_sign]]
+export def PSS_SIGNBUFSZ = PRIVEXP_BUFSZ;
+
+// Signs the hash 'msghash' using the private key 'privkey' by applying the PSS
+// signature scheme as defined in RFC 8017. 'sig' must be in the the size of the
+// modulus n (see [[privkey_nsize]])
+//
+// It is recommended that 'hf' is the same hash function that was used to
+// generate 'msgmhash'. 'buf' needs to be at least the size of
+// [[PSS_SIGNBUFSZ]]. 'rand' must be an [[io::reader]] that returns a
+// cryptographiclly random data on read like [[crypto::random::stream]]. The
+// expected size of the salt is provided with 'saltsz'. Default is the maximum
+// possible salt size.
+//
+// Returns [[errors::invalid]], if one of the parameters are invalid.
+// [[errors::overflow]] is returned, if 'buf' is to small. Errors that occur by
+// reading from 'rand' are returned as [[io::error]].
+export fn pss_sign(
+ privkey: []u8,
+ msghash: []u8,
+ sig: []u8,
+ hf: *hash::hash,
+ rand: io::handle,
+ buf: []u8,
+ saltsz: (size | default) = default,
+) (void | error | io::error) = {
+ let priv = privkey_params(privkey);
+
+ // Use var names that match the rfc.
+ let embits = priv.nbitlen - 1;
+ if (len(sig) != (embits + 7) / 8) {
+ return errors::invalid: error;
+ };
+ let em = sig;
+ let hlen = len(msghash);
+ let emlen = len(em);
+ const slen = dsaltsz(len(sig), hlen, saltsz);
+
+ if (emlen < hlen + slen + 2) {
+ return errors::invalid: error;
+ };
+
+ let db = em[..emlen - hlen - 1];
+ db[..] = [0...];
+ db[len(db) - slen - 1] = 0x01;
+ let salt = db[len(db) - slen..];
+ io::readall(rand, salt)?;
+
+ let h = em[emlen - hlen - 1..emlen - 1];
+ const padding: [8]u8 = [0...];
+ hash::reset(hf);
+ hash::write(hf, padding);
+ hash::write(hf, msghash);
+ hash::write(hf, salt);
+ hash::sum(hf, h);
+
+ mgfxor(db, hf, h, buf);
+
+ em[0] &= 0xff >> (8*emlen - embits): u8;
+ em[len(db)..emlen - 1] = h[..];
+ em[emlen - 1] = 0xbc;
+
+ privexp(&priv, em, buf)?;
+};
+
+fn dsaltsz(nsz: size, hsz: size, s: (size | default)) size = {
+ match (s) {
+ case let s: size =>
+ return s;
+ case default =>
+ return nsz - hsz - 2;
+ };
+};
+
+// Verifies a PSS signature 'sig' of the mesage hash 'msghash' using the public
+// key 'pupkey' as defined in RFC 8017.
+//
+// 'hf' must be the hash that was used to create the signature. 'buf' needs to
+// be at least the size of [[PSS_VERIFYBUFSZ]]. The expected size of the salt is
+// provided with 'saltsz'. Default is the maximum possible salt size. The
+// function will fail, if the signature's salt size does not match the expected.
+//
+// Returns [[badsig]], if the signature verification fails. [[errors::overflow]]
+// is returned, if 'buf' is to small.
+export fn pss_verify(
+ pubkey: []u8,
+ msghash: []u8,
+ sig: []u8,
+ hf: *hash::hash,
+ buf: []u8,
+ saltsz: (size | default) = default,
+) (void | error) = {
+ let pub = pubkey_params(pubkey);
+ if (len(sig) != len(pub.n)) {
+ return badsig;
+ };
+
+ // rename some variables to match the ones in the RFC
+ let mhash = msghash;
+ const hlen = hash::sz(hf);
+ const slen = dsaltsz(len(pub.n), hlen, saltsz);
+ let em = buf[..len(sig)];
+ const emlen = len(em);
+ em[..] = sig[..];
+
+ if (emlen < hlen + slen + 2) {
+ return badsig;
+ };
+
+ let pubbuf = buf[len(sig)..];
+ match (pubexp(&pub, em, pubbuf)) {
+ case void => void;
+ case errors::invalid =>
+ return badsig;
+ case let e: error =>
+ return e;
+ };
+
+ if (em[emlen - 1] != 0xbc) {
+ return badsig;
+ };
+
+ const maskdbsz = emlen - hlen - 1;
+ let maskeddb = em[..maskdbsz];
+ let h = em[maskdbsz..maskdbsz + hlen];
+
+ const embitlen = pubkey_nbitlen(pubkey) - 1;
+ const zerobitsh = 8 - (8*len(em) - embitlen);
+ if (maskeddb[0] >> zerobitsh != 0) {
+ return badsig;
+ };
+
+ let db = maskeddb;
+ mgfxor(db, hf, h, pubbuf);
+ db[0] &= 0xff >> (8*len(em) - embitlen): u8;
+
+ const seppos = len(em) - hlen - slen - 2;
+ for (let i = 0z; i < seppos; i += 1) {
+ if (db[i] != 0x00) {
+ return badsig;
+ };
+ };
+ if (db[seppos] != 0x01) {
+ return badsig;
+ };
+
+ const salt = db[len(db) - slen..];
+ const padding: [8]u8 = [0...];
+ hash::reset(hf);
+ hash::write(hf, padding);
+ hash::write(hf, mhash);
+ hash::write(hf, salt);
+
+ let genh = pubbuf[..hlen];
+ hash::sum(hf, genh);
+
+ if (math::eqslice(genh, h) != 1) {
+ return badsig;
+ };
+};
+
+// dest = dest XOR mgf(h, seed, len(dest)). 'buf' must be hash::sz(h) bytes
+// long.
+fn mgfxor(dest: []u8, h: *hash::hash, seed: []u8, buf: []u8) void = {
+ assert(len(buf) >= hash::sz(h));
+
+ let ctrbuf: [4]u8 = [0...];
+ let sum = buf[..hash::sz(h)];
+ const iterations = (len(dest) + len(sum) - 1) / len(sum);
+
+ for (let ctr: u32 = 0; ctr < iterations; ctr += 1) {
+ endian::beputu32(ctrbuf, ctr);
+ hash::reset(h);
+ hash::write(h, seed);
+ hash::write(h, ctrbuf);
+ hash::sum(h, sum);
+
+ const start = ctr * len(sum);
+ const remain = len(dest) - start;
+ const chunksz = if (remain < len(sum)) remain else len(sum);
+
+ let chunk = dest[start..start + chunksz];
+ math::xor(chunk, chunk, sum[..chunksz]);
+ };
+
+ bytes::zero(sum);
+};