1 # Digests, HMAC, HKDF, PBKDF2, scrypt, AEAD, and RSA and ECDSA signatures through OpenSSL 3 libcrypto, plus OS secure random bytes. Source: https://github.com/paymog/bend-kit/tree/main/crypto 2 import Base 3 4 # Bytes travel as (len, words): len octets, four to a U32 word, low byte first, as Wire.recv.words gives them. 5 # The libcrypto effects fail with ENOENT when libcrypto.3 does not load; set BEND_LIBCRYPTO to its path. 6 7 # alg is an OpenSSL digest name, such as "SHA256" or "SHA3-256". Fails with EINVAL for an unknown name. 8 def digest.words(alg: String, len: U32, words: Array) -> 9 IO(Result<&1, &1, U32 & String, U32 & Array>): 10 import "./effs/crypto.c" 11 import "./effs/crypto.js" 12 13 def sha256.words(len: U32, words: Array) -> IO(Result<&1, &1, U32 & String, U32 & Array>): 14 digest.words("SHA256", len, words) 15 16 def sha512.words(len: U32, words: Array) -> IO(Result<&1, &1, U32 & String, U32 & Array>): 17 digest.words("SHA512", len, words) 18 19 # Legacy: SHA-1 is broken for collisions. Use it only where a protocol needs it, such as the WebSocket handshake. 20 def sha1.words(len: U32, words: Array) -> IO(Result<&1, &1, U32 & String, U32 & Array>): 21 digest.words("SHA1", len, words) 22 23 # HMAC (RFC 2104) of data under key, with the digest alg. 24 def hmac.words(alg: String, klen: U32, key: Array, dlen: U32, data: Array) -> 25 IO(Result<&1, &1, U32 & String, U32 & Array>): 26 import "./effs/crypto.c" 27 import "./effs/crypto.js" 28 29 # HKDF (RFC 5869): n octets from ikm, salt, and info. Fails with EINVAL when n is 0 or more than 255 digest lengths. 30 def hkdf.words(alg: String, slen: U32, salt: Array, klen: U32, ikm: Array, ilen: U32, info: Array, n: U32) -> 31 IO(Result<&1, &1, U32 & String, U32 & Array>): 32 import "./effs/crypto.c" 33 import "./effs/crypto.js" 34 35 # PBKDF2 (RFC 8018 §5.2) with HMAC over alg: n octets from pass and salt. Fails with EINVAL when iters or n is 0. 36 def pbkdf2.words(alg: String, plen: U32, pass: Array, slen: U32, salt: Array, iters: U32, n: U32) -> 37 IO(Result<&1, &1, U32 & String, U32 & Array>): 38 import "./effs/crypto.c" 39 import "./effs/crypto.js" 40 41 # Scrypt (RFC 7914): N >= 2 is a power of two; r and p are positive. 42 # maxmem bounds the scrypt workspace plus output in octets; zero or over-budget costs fail with EINVAL. 43 def scrypt.words(plen: U32, pass: Array, slen: U32, salt: Array, 44 N: U32, r: U32, p: U32, maxmem: U32, n: U32) -> 45 IO(Result<&1, &1, U32 & String, U32 & Array>): 46 import "./effs/crypto.c" 47 import "./effs/crypto.js" 48 49 # n octets from the OS secure random source (getentropy, crypto.getRandomValues). Needs no libcrypto. 50 def random.words(n: U32) -> IO(Result<&1, &1, U32 & String, U32 & Array>): 51 import "./effs/crypto.c" 52 import "./effs/crypto.js" 53 54 # Equal octets, in time that depends only on the lengths. Use it to compare MACs. 55 def eq.ct.words(alen: U32, a: Array, blen: U32, b: Array) -> IO(Bool): 56 import "./effs/crypto.c" 57 import "./effs/crypto.js" 58 59 # Signatures. Sign keys are unencrypted PEM private keys (PKCS#8 "PRIVATE KEY", or "RSA PRIVATE KEY" / "EC PRIVATE KEY"); 60 # verify keys are PEM SubjectPublicKeyInfo ("PUBLIC KEY"), or JWK parts as big-endian octets (RFC 7518 §6). 61 # Each key type is fixed per def, so a key of the other type fails: RSA needs 2048 bits or more, ECDSA needs P-256. 62 # Verify gives Done False for any wrong signature, whatever its length; Fail EINVAL is for a bad alg or key. 63 64 # RSASSA-PKCS1-v1_5 (RS256, RS384, RS512): alg is "SHA256", "SHA384", or "SHA512". Gives a modulus-length signature. 65 def rsa.sign.words(alg: String, klen: U32, key: Array, dlen: U32, data: Array) -> 66 IO(Result<&1, &1, U32 & String, U32 & Array>): 67 import "./effs/crypto.c" 68 import "./effs/crypto.js" 69 70 def rsa.verify.words(alg: String, klen: U32, key: Array, dlen: U32, data: Array, slen: U32, sig: Array) -> 71 IO(Result<&1, &1, U32 & String, Bool>): 72 import "./effs/crypto.c" 73 import "./effs/crypto.js" 74 75 # n and e of a JWK "RSA" key. e must be odd and at least 3. 76 def rsa.verify.jwk.words(alg: String, nlen: U32, n: Array, elen: U32, e: Array, dlen: U32, data: Array, slen: U32, sig: Array) -> 77 IO(Result<&1, &1, U32 & String, Bool>): 78 import "./effs/crypto.c" 79 import "./effs/crypto.js" 80 81 # ECDSA on P-256 (ES256): alg is "SHA256". Signatures are JOSE raw r || s, 64 octets (RFC 7518 §3.4), not DER. 82 def ecdsa.sign.words(alg: String, klen: U32, key: Array, dlen: U32, data: Array) -> 83 IO(Result<&1, &1, U32 & String, U32 & Array>): 84 import "./effs/crypto.c" 85 import "./effs/crypto.js" 86 87 def ecdsa.verify.words(alg: String, klen: U32, key: Array, dlen: U32, data: Array, slen: U32, sig: Array) -> 88 IO(Result<&1, &1, U32 & String, Bool>): 89 import "./effs/crypto.c" 90 import "./effs/crypto.js" 91 92 # x and y of a JWK "EC" key on "P-256", 32 octets each. 93 def ecdsa.verify.jwk.words(alg: String, xlen: U32, x: Array, ylen: U32, y: Array, dlen: U32, data: Array, slen: U32, sig: Array) -> 94 IO(Result<&1, &1, U32 & String, Bool>): 95 import "./effs/crypto.c" 96 import "./effs/crypto.js" 97 98 # AEAD: alg is "AES-256-GCM" or "CHACHA20-POLY1305". Key: 32 octets; nonce: 12 octets. 99 # Seal returns ciphertext followed by a 16-octet tag; open verifies the tag before returning plaintext. 100 # Never reuse a (key, nonce) pair. Bad lengths, algorithm or authentication fail with EINVAL. 101 def aead.seal.words(alg: String, klen: U32, key: Array, nlen: U32, nonce: Array, 102 alen: U32, aad: Array, dlen: U32, data: Array) -> 103 IO(Result<&1, &1, U32 & String, U32 & Array>): 104 import "./effs/crypto.c" 105 import "./effs/crypto.js" 106 107 def aead.open.words(alg: String, klen: U32, key: Array, nlen: U32, nonce: Array, 108 alen: U32, aad: Array, dlen: U32, data: Array) -> 109 IO(Result<&1, &1, U32 & String, U32 & Array>): 110 import "./effs/crypto.c" 111 import "./effs/crypto.js"