Skip to content

Commit ffbd2a8

Browse files
Add more bindings to remaining classes
1 parent 92ccc1b commit ffbd2a8

11 files changed

+719
-0
lines changed

spago.dhall

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
[ "effect"
44
, "either"
55
, "exceptions"
6+
, "foreign"
67
, "maybe"
78
, "node-buffer"
89
, "node-streams"

src/Node/Crypto/KeyObject.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as crypto from "node:crypto";
2+
3+
export const eqKeyObjectImpl = (a, b) => a.equals(b);
4+
5+
export const newSecretKeyImpl = (buf) => crypto.newSecretKey(buf);
6+
export const newPublicKeyBufImpl = (buf) => crypto.newPublicKey(buf);
7+
export const newPublicKeyObjImpl = (obj) => crypto.newPublicKey(obj);
8+
export const newPrivateKeyBufImpl = (buf) => crypto.newPrivateKey(buf);
9+
export const newPrivateKeyObjImpl = (obj) => crypto.newPrivateKey(obj);
10+
11+
export const assymetricKeyDetails = (ko) => ko.assymetricKeyDetails;
12+
export const assymetricKeyType = (ko) => ko.assymetricKeyType;
13+
export const symmetricKeySize = (ko) => ko.symmetricKeySize;
14+
export const typeImpl = (ko) => ko.type;

src/Node/Crypto/KeyObject.purs

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
module Node.Crypto.KeyObject
2+
( KeyObjectType
3+
, Symmetric
4+
, Asymmetric
5+
, AsymmetricKeyType
6+
, Private
7+
, Public
8+
, KeyObject
9+
, KeyFormat
10+
, pem
11+
, der
12+
, jwk
13+
, DerKeyType
14+
, pkcs1
15+
, spki
16+
, newSecretKey
17+
, newPublicKeyBuf
18+
, newPublicKeyObj
19+
, NewPublicKeyOptions
20+
, newPrivateKeyBuf
21+
, newPrivateKeyObj
22+
, NewPrivateKeyOptions
23+
, assymetricKeyDetails
24+
, assymetricKeyType
25+
, symmetricKeySize
26+
, type'
27+
, keyType
28+
) where
29+
30+
import Prelude
31+
32+
import Data.Function.Uncurried (Fn2, runFn2)
33+
import Effect (Effect)
34+
import Effect.Uncurried (EffectFn1, runEffectFn1)
35+
import Foreign (Foreign)
36+
import Node.Buffer (Buffer)
37+
import Partial.Unsafe (unsafeCrashWith)
38+
import Prim.Row as Row
39+
import Unsafe.Coerce (unsafeCoerce)
40+
41+
data KeyObjectType
42+
43+
foreign import data Symmetric :: KeyObjectType
44+
foreign import data Asymmetric :: AsymmetricKeyType -> KeyObjectType
45+
46+
data AsymmetricKeyType
47+
48+
foreign import data Private :: AsymmetricKeyType
49+
foreign import data Public :: AsymmetricKeyType
50+
51+
-- | Node.js uses a `KeyObject` class to represent a symmetric or asymmetric key, and each kind of key exposes different functions.
52+
-- |
53+
-- | Most applications should consider using the new `KeyObject` API instead of passing keys as strings or Buffers
54+
-- | due to improved security features.
55+
-- |
56+
-- | `KeyObject` instances can be passed to other threads via `postMessage()`.
57+
-- | The receiver obtains a cloned `KeyObject`, and the `KeyObject` does not need to be listed in the `transferList` argument.
58+
foreign import data KeyObject :: KeyObjectType -> Type
59+
60+
instance Eq (KeyObject a) where
61+
eq a b = runFn2 eqKeyObjectImpl a b
62+
63+
foreign import eqKeyObjectImpl :: forall a. Fn2 (KeyObject a) (KeyObject a) Boolean
64+
65+
newtype KeyFormat = KeyFormat String
66+
67+
derive newtype instance Eq KeyFormat
68+
derive newtype instance Ord KeyFormat
69+
derive newtype instance Show KeyFormat
70+
71+
pem = KeyFormat "pem" :: KeyFormat
72+
der = KeyFormat "der" :: KeyFormat
73+
jwk = KeyFormat "jwk" :: KeyFormat
74+
75+
newtype DerKeyType = DerKeyType String
76+
77+
derive newtype instance Eq DerKeyType
78+
derive newtype instance Ord DerKeyType
79+
derive newtype instance Show DerKeyType
80+
81+
pkcs1 = DerKeyType "pkcs1" :: DerKeyType
82+
spki = DerKeyType "spki" :: DerKeyType
83+
84+
foreign import newSecretKeyImpl :: EffectFn1 (Buffer) ((KeyObject Symmetric))
85+
86+
-- | Creates and returns a new key object containing a secret key for symmetric encryption or Hmac.
87+
newSecretKey :: Buffer -> Effect (KeyObject Symmetric)
88+
newSecretKey key = runEffectFn1 newSecretKeyImpl key
89+
90+
foreign import newPublicKeyBufImpl :: EffectFn1 (Buffer) (KeyObject (Asymmetric Public))
91+
92+
-- | Creates and returns a new key object containing a public key. Format is assumed to be 'pem';
93+
newPublicKeyBuf :: Buffer -> Effect (KeyObject (Asymmetric Public))
94+
newPublicKeyBuf buffer = runEffectFn1 newPublicKeyBufImpl buffer
95+
96+
foreign import newPublicKeyObjImpl :: forall r. EffectFn1 ({ | r }) ((KeyObject (Asymmetric Public)))
97+
98+
-- | Creates and returns a new key object containing a public key.
99+
newPublicKeyObj :: forall r trash. Row.Union r trash NewPublicKeyOptions => { | r } -> Effect (KeyObject (Asymmetric Public))
100+
newPublicKeyObj r = runEffectFn1 newPublicKeyObjImpl r
101+
102+
type NewPublicKeyOptions =
103+
( key :: Buffer
104+
, format :: KeyFormat
105+
, type :: DerKeyType
106+
)
107+
108+
foreign import newPrivateKeyBufImpl :: EffectFn1 (Buffer) ((KeyObject (Asymmetric Private)))
109+
110+
-- | Creates and returns a new key object containing a private key. Format is assumed to be 'pem';
111+
newPrivateKeyBuf :: Buffer -> Effect (KeyObject (Asymmetric Private))
112+
newPrivateKeyBuf buffer = runEffectFn1 newPrivateKeyBufImpl buffer
113+
114+
foreign import newPrivateKeyObjImpl :: forall r. EffectFn1 ({ | r }) ((KeyObject (Asymmetric Private)))
115+
116+
-- | Creates and returns a new key object containing a private key.
117+
-- | If the private key is encrypted, a passphrase must be specified. The length of the passphrase is limited to 1024 bytes.
118+
newPrivateKeyObj :: forall r trash. Row.Union r trash NewPrivateKeyOptions => { | r } -> Effect (KeyObject (Asymmetric Private))
119+
newPrivateKeyObj r = runEffectFn1 newPrivateKeyObjImpl r
120+
121+
type NewPrivateKeyOptions =
122+
( key :: Buffer
123+
, format :: KeyFormat
124+
, type :: DerKeyType
125+
, passphrase :: Buffer
126+
)
127+
128+
129+
-- | Returns an object whose fields depend on the algorithm of the `KeyObject`
130+
-- | - `modulusLength`: `number` Key size in bits (RSA, DSA).
131+
-- | - `publicExponent`: `bigint` Public exponent (RSA).
132+
-- | - `hashAlgorithm`: `string` Name of the message digest (RSA-PSS).
133+
-- | - `mgf1HashAlgorithm`: `string` Name of the message digest used by MGF1 (RSA-PSS).
134+
-- | - `saltLength`: `number` Minimal salt length in bytes (RSA-PSS).
135+
-- | - `divisorLength`: `number` Size of q in bits (DSA).
136+
-- | - `namedCurve`: `string` Name of the curve (EC).
137+
-- |
138+
-- | This property exists only on asymmetric keys. Depending on the type of the key, this object contains information about the key. None of the information obtained through this property can be used to uniquely identify a key or to compromise the security of the key.
139+
-- |
140+
-- | For RSA-PSS keys, if the key material contains a RSASSA-PSS-params sequence, the hashAlgorithm, mgf1HashAlgorithm, and saltLength properties will be set.
141+
-- |
142+
-- | Other key details might be exposed via this API using additional attributes.
143+
foreign import assymetricKeyDetails :: forall x. KeyObject (Asymmetric x) -> Foreign
144+
145+
-- | For asymmetric keys, this property represents the type of the key. Supported key types are:
146+
-- | - 'rsa' (OID 1.2.840.113549.1.1.1)
147+
-- | - 'rsa-pss' (OID 1.2.840.113549.1.1.10)
148+
-- | - 'dsa' (OID 1.2.840.10040.4.1)
149+
-- | - 'ec' (OID 1.2.840.10045.2.1)
150+
-- | - 'x25519' (OID 1.3.101.110)
151+
-- | - 'x448' (OID 1.3.101.111)
152+
-- | - 'ed25519' (OID 1.3.101.112)
153+
-- | - 'ed448' (OID 1.3.101.113)
154+
-- | - 'dh' (OID 1.2.840.113549.1.3.1)
155+
-- | This property is undefined for unrecognized KeyObject types and symmetric keys.
156+
foreign import assymetricKeyType :: forall x. KeyObject (Asymmetric x) -> String
157+
158+
-- | For secret keys, this property represents the size of the key in bytes. This property is undefined for asymmetric keys.
159+
foreign import symmetricKeySize :: KeyObject Symmetric -> Number
160+
161+
foreign import typeImpl :: forall a. KeyObject a -> String
162+
163+
-- | Depending on the type of this KeyObject, this property is either 'secret' for secret (symmetric) keys,
164+
-- | 'public' for public (asymmetric) keys or 'private' for private (asymmetric) keys.
165+
type' :: forall a. KeyObject a -> String
166+
type' ko = typeImpl ko
167+
168+
-- | Eliminator for a KeyObject when one doesn't know what the `type` is.
169+
keyType
170+
:: forall x a
171+
. KeyObject x
172+
-> (KeyObject Symmetric -> a)
173+
-> (KeyObject (Asymmetric Public) -> a)
174+
-> (KeyObject (Asymmetric Private) -> a)
175+
-> a
176+
keyType ko onSym onPub onPriv = case type' ko of
177+
"secret" -> onSym $ (unsafeCoerce :: _ -> KeyObject Symmetric) ko
178+
"public" -> onPub $ (unsafeCoerce :: _ -> KeyObject (Asymmetric Public)) ko
179+
"private" -> onPriv $ (unsafeCoerce :: _ -> KeyObject (Asymmetric Private)) ko
180+
ty -> unsafeCrashWith $ "Impossible key type: " <> ty

src/Node/Crypto/RsaOptions.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as crypto from "node:crypto";
2+
3+
export const js_rsa_pkcs1_padding = crypto.constants.RSA_PKCS1_PADDING;
4+
export const js_rsa_pkcs1_pss_padding = crypto.constants.RSA_PKCS1_PSS_PADDING;
5+
6+
export const js_rsa_pss_saltlen_digest = crypto.constants.RSA_PSS_SALTLEN_DIGEST;
7+
export const js_rsa_pss_saltlen_max_sign = crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN;

src/Node/Crypto/RsaOptions.purs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
-- | These constants are used in `Sign.sign` and `Verify.verify`.
2+
module Node.Crypto.RsaOptions
3+
( SignPadding
4+
, rsa_pkcs1_padding
5+
, rsa_pkcs1_pss_padding
6+
, SaltLength
7+
, rsa_pss_saltlen_digest
8+
, rsa_pss_saltlen_max_sign
9+
) where
10+
11+
import Prelude
12+
13+
newtype SignPadding = SignPadding Int
14+
15+
derive newtype instance Eq SignPadding
16+
derive newtype instance Ord SignPadding
17+
derive newtype instance Show SignPadding
18+
19+
foreign import js_rsa_pkcs1_padding :: Int
20+
rsa_pkcs1_padding = SignPadding js_rsa_pkcs1_padding :: SignPadding
21+
22+
foreign import js_rsa_pkcs1_pss_padding :: Int
23+
rsa_pkcs1_pss_padding = SignPadding js_rsa_pkcs1_pss_padding :: SignPadding
24+
25+
newtype SaltLength = SaltLength Int
26+
27+
derive newtype instance Eq SaltLength
28+
derive newtype instance Ord SaltLength
29+
derive newtype instance Show SaltLength
30+
31+
foreign import js_rsa_pss_saltlen_digest :: Int
32+
rsa_pss_saltlen_digest = SaltLength js_rsa_pss_saltlen_digest :: SaltLength
33+
34+
foreign import js_rsa_pss_saltlen_max_sign :: Int
35+
rsa_pss_saltlen_max_sign = SaltLength js_rsa_pss_saltlen_max_sign :: SaltLength

src/Node/Crypto/Sign.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as crypto from "node:crypto";
2+
3+
export const newImpl = (algorithm) => crypto.createSign(algorithm);
4+
export const updateBufImpl = (sign, key) => sign.update(key);
5+
export const updateStrImpl = (sign, key, enc) => sign.update(key, enc);
6+
export const signKeyObjectImpl = (sign, keyObj) => sign.sign(keyObj);
7+
export const signObjImpl = (sign, obj) => sign.sign(obj);

src/Node/Crypto/Sign.purs

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
module Node.Crypto.Sign
2+
( Sign
3+
, new
4+
, updateBuf
5+
, updateStr
6+
, sign
7+
, SignOptions
8+
, signObj
9+
) where
10+
11+
import Prelude
12+
13+
import Effect (Effect)
14+
import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, runEffectFn1, runEffectFn2, runEffectFn3)
15+
import Node.Buffer (Buffer)
16+
import Node.Crypto.KeyObject (Asymmetric, KeyObject, NewPrivateKeyOptions, Private)
17+
import Node.Crypto.RsaOptions (SaltLength, SignPadding)
18+
import Node.Encoding (Encoding, encodingToNode)
19+
import Prim.Row as Row
20+
21+
-- | The `Sign` class is a utility for generating signatures. It can be used in one of two ways:
22+
-- | - As a writable stream, where data to be signed is written and the `sign.sign()` method is used to generate and return the signature, or
23+
-- | - Using the `sign.update()` and `sign.sign()` methods to produce the signature.
24+
-- | The `crypto.createSign()` method is used to create Sign instances.
25+
-- |
26+
-- | Note: these PureScript bindings do not provide an option for the Writable stream options variant of `createSign`.
27+
foreign import data Sign :: Type
28+
29+
foreign import newImpl :: EffectFn1 (String) (Sign)
30+
31+
-- | Creates and returns a `Sign` object that uses the given algorithm.
32+
-- | Use `crypto.getHashes()` to obtain the names of the available digest algorithms.
33+
-- |
34+
-- | In some cases, a `Sign` instance can be created using the name of a signature algorithm,
35+
-- | such as 'RSA-SHA256', instead of a digest algorithm.
36+
-- | This will use the corresponding digest algorithm. This does not work for all signature algorithms,
37+
-- | such as 'ecdsa-with-SHA256', so it is best to always use digest algorithm names.
38+
new :: String -> Effect Sign
39+
new algorithm = runEffectFn1 newImpl algorithm
40+
41+
foreign import updateBufImpl :: EffectFn2 (Sign) (Buffer) (Unit)
42+
43+
-- | Updates the Sign content with the given data
44+
updateBuf :: Buffer -> Sign -> Effect Unit
45+
updateBuf buffer signVal =
46+
runEffectFn2 updateBufImpl signVal buffer
47+
48+
foreign import updateStrImpl :: EffectFn3 (Sign) (String) (String) (Unit)
49+
50+
-- | Updates the Sign content with the given data
51+
updateStr :: String -> Encoding -> Sign -> Effect Unit
52+
updateStr dat enc signVal =
53+
runEffectFn3 updateStrImpl signVal dat (encodingToNode enc)
54+
55+
foreign import signKeyObjectImpl :: EffectFn2 (Sign) (KeyObject (Asymmetric Private)) (Buffer)
56+
57+
-- | Calculates the signature on all the data passed through using either `sign.update()` or `sign.write()`.
58+
-- |
59+
-- | The `Sign` object can not be again used after `sign.sign()` method has been called. Multiple calls to `sign.sign()` will result in an error being thrown.
60+
sign :: KeyObject (Asymmetric Private) -> Sign -> Effect Buffer
61+
sign keyObject signVal =
62+
runEffectFn2 signKeyObjectImpl signVal keyObject
63+
64+
foreign import signObjImpl :: forall r. EffectFn2 (Sign) ({ | r }) (Buffer)
65+
66+
-- | Options for DSA and ECDSA:
67+
-- | `dsaEncoding`: For DSA and ECDSA, this option specifies the format of the generated signature. It can be one of the following:
68+
-- | - 'der' (default): DER-encoded ASN.1 signature structure encoding (r, s).
69+
-- | - 'ieee-p1363': Signature format r || s as proposed in IEEE-P1363.
70+
-- |
71+
-- | Options for RSA:
72+
-- | `padding`: `RSA_PKCS1_PSS_PADDING` will use `MGF1` with the same hash function used to sign the message
73+
-- | as specified in section 3.1 of [RFC 4055](https://www.rfc-editor.org/rfc/rfc4055.txt),
74+
-- | unless an MGF1 hash function has been specified as
75+
-- | part of the key in compliance with section 3.3 of [RFC 4055](https://www.rfc-editor.org/rfc/rfc4055.txt).
76+
-- | `saltLength`: Salt length for when padding is `RSA_PKCS1_PSS_PADDING`.
77+
-- | The special value `crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest size,
78+
-- | `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the maximum permissible value.
79+
type SignOptions =
80+
( dsaEncoding :: String
81+
, padding :: SignPadding
82+
, saltLength :: SaltLength
83+
| NewPrivateKeyOptions
84+
)
85+
86+
signObj :: forall r trash. Row.Union r trash SignOptions => { | r } -> Sign -> Effect Buffer
87+
signObj r signVal =
88+
runEffectFn2 signObjImpl signVal r

src/Node/Crypto/Verify.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as crypto from "node:crypto";
2+
3+
export const newImpl = (algorithm) => crypto.createVerify(algorithm);
4+
export const updateBufImpl = (verify, buf) => verify.update(buf);
5+
export const updateStrImpl = (verify, str, enc) => verify.update(str, enc);
6+
export const verifyKeyObjectImpl = (verify, keyObject, sig) => verify.verify(keyObject, sig);
7+
export const verifyObjImpl = (verify, obj, sig) => verify.verify(obj, sig);

0 commit comments

Comments
 (0)