hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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:
Acrypto/rsa/+test/pss_test.ha | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrypto/rsa/pss.ha | 203+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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); +};