crypto.bend

bend-kit-crypto@0.2.2.2 · 111 lines · 18 definitions

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
2import 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.
8def digest.words(alg: String, len: U32, words: Array<U32>) ->
9  IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
10  import "./effs/crypto.c"
11  import "./effs/crypto.js"
12
13def sha256.words(len: U32, words: Array<U32>) -> IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
14  digest.words("SHA256", len, words)
15
16def sha512.words(len: U32, words: Array<U32>) -> IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
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.
20def sha1.words(len: U32, words: Array<U32>) -> IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
21  digest.words("SHA1", len, words)
22
23# HMAC (RFC 2104) of data under key, with the digest alg.
24def hmac.words(alg: String, klen: U32, key: Array<U32>, dlen: U32, data: Array<U32>) ->
25  IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
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.
30def hkdf.words(alg: String, slen: U32, salt: Array<U32>, klen: U32, ikm: Array<U32>, ilen: U32, info: Array<U32>, n: U32) ->
31  IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
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.
36def pbkdf2.words(alg: String, plen: U32, pass: Array<U32>, slen: U32, salt: Array<U32>, iters: U32, n: U32) ->
37  IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
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.
43def scrypt.words(plen: U32, pass: Array<U32>, slen: U32, salt: Array<U32>,
44  N: U32, r: U32, p: U32, maxmem: U32, n: U32) ->
45  IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
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.
50def random.words(n: U32) -> IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
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.
55def eq.ct.words(alen: U32, a: Array<U32>, blen: U32, b: Array<U32>) -> 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.
65def rsa.sign.words(alg: String, klen: U32, key: Array<U32>, dlen: U32, data: Array<U32>) ->
66  IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
67  import "./effs/crypto.c"
68  import "./effs/crypto.js"
69
70def rsa.verify.words(alg: String, klen: U32, key: Array<U32>, dlen: U32, data: Array<U32>, slen: U32, sig: Array<U32>) ->
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.
76def rsa.verify.jwk.words(alg: String, nlen: U32, n: Array<U32>, elen: U32, e: Array<U32>, dlen: U32, data: Array<U32>, slen: U32, sig: Array<U32>) ->
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.
82def ecdsa.sign.words(alg: String, klen: U32, key: Array<U32>, dlen: U32, data: Array<U32>) ->
83  IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
84  import "./effs/crypto.c"
85  import "./effs/crypto.js"
86
87def ecdsa.verify.words(alg: String, klen: U32, key: Array<U32>, dlen: U32, data: Array<U32>, slen: U32, sig: Array<U32>) ->
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.
93def ecdsa.verify.jwk.words(alg: String, xlen: U32, x: Array<U32>, ylen: U32, y: Array<U32>, dlen: U32, data: Array<U32>, slen: U32, sig: Array<U32>) ->
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.
101def aead.seal.words(alg: String, klen: U32, key: Array<U32>, nlen: U32, nonce: Array<U32>,
102  alen: U32, aad: Array<U32>, dlen: U32, data: Array<U32>) ->
103  IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
104  import "./effs/crypto.c"
105  import "./effs/crypto.js"
106
107def aead.open.words(alg: String, klen: U32, key: Array<U32>, nlen: U32, nonce: Array<U32>,
108  alen: U32, aad: Array<U32>, dlen: U32, data: Array<U32>) ->
109  IO(Result<&1, &1, U32 & String, U32 & Array<U32>>):
110  import "./effs/crypto.c"
111  import "./effs/crypto.js"