pscl.js and the ProScript API¶
Using pscl.js¶
We do not use pscl.js in dirsp-exchange! The ProScript Cryptography Library (PSCL) is used as an API during ProVerif proof
testing, and we use it as an API
(see the OCaml library dirsp-proscript
). There is a dirsp-proscript-mirage
that uses the Mirage crypto libraries,
which themselves use a correct-by-construction implementation from fiat-crypto
for its elliptic curve functions.
We decided to abandon pscl.js after trying to compare its output with ours. Some cautionary notes:
Comparing dirsp-exchange’s output to sp.js + pscl.js is a bit of a fool’s errand. After burning two days trying to match results, I realized that pscl.js’ DH25519 was non-standard. In fact, pscl.js is not used for the security proofs (the ProVerif translation will replace references to PSCL functions like DH25519). See comment about DH25519 in https://github.com/Inria-Prosecco/proscript-messaging/issues/1 ; I had likewise ran test vectors through pscl.js and its DH25519 failed.
It was difficult to understand which parameters to pscl.js need to be hex-encoded and which ones don’t. And it really matters in JavaScript. In node.js, the
new Buffer(some_variable, 'hex')
construction used frequently in pscl.js will silently return an empty buffer if the input is not hex. For example, supplying values without hex encoding toProScript.crypto.ED25519.signature
would result in a call toProScript.crypto.ED25519Hash(sk)
, which in turn would return a SHA512 hash of an empty value because of the Buffer-based hex decoding. Yet the ProVerif code does not hex encode thatsk
parameter when calling theProScript.crypto.ED25519.signature
API.Completely as a reaction to hex-encoding ambiguities in pscl.js, nothing is expected to be hex encoded in
dirsp-proscript
. Thedirsp-proscript
API is just raw bytes. However, the hex encoderstoBitstring
andfromBitstring
used in the higher-level KBB2017 algorithm (ps/sp.js
) are still present.
So there is no need for pscl.js, and you won’t get matching results because the kinda critical Diffie-Hellman implementation in pscl.js is non-standard and perhaps broken.
Sidebar: None of the above caution affects the KBB2017 proof
Now that the cautionary preamble is out of the way … here is how you would run sp.js + pscl.js.
You’ll need node.js v10+ installed.
Then:
npm install --save-dev window hexy
npm install --save-dev @peculiar/webcrypto # only needed if node.js v14 or earlier
node --experimental-repl-await # or 'nvm exec 16.0 node --experimental-repl-await' depending on your installation
Within node
:
// EITHER: this section is for node.js v10-v14
const getRandomValues = await require('get-random-values')
// OR: this section is for node.js v15+ which has built-in (more vetted) webcrypto support
const { getRandomValues } = await require('crypto').webcrypto
// THEN continue with the following ...
// Make an approximation of the browser
const Window = await require('window')
const { createHash, createHmac, createCipheriv, createDecipheriv } = await require('crypto');
const window = new Window()
window.crypto = { getRandomValues }
const NodeCrypto = { createHash, createHmac, createCipheriv, createDecipheriv }
// Load the Javascript files
const fs = await require("fs")
const pscl_js = fs.readFileSync('./src-proscript/proscript-messaging/pscl/pscl.js', 'utf8')
eval(pscl_js)
const sp_js = fs.readFileSync('./src-proscript/proscript-messaging//ps/sp.js', 'utf8')
eval(sp_js.replaceAll(/\bconst /g,"var "))
// Now you have access to the ProScript Cryptographic Library (pscl.js)
// and the 'Signal Protocol'/KBB2017 (sp.js)
ProScript.crypto.random16Bytes()
Type_key.construct()
const { hexy } = await require('hexy')
hexy(Type_key.construct())
// You can also override the random functions so you can do a
// repeatable comparison between JavaScript and OCaml
const firstByteMd5 = function(s) { return NodeCrypto.createHash('md5').update(s).digest()[0] }
ProScript.crypto.random12Bytes = function(id) { const fb = firstByteMd5(id); return Array.from({length: 12}, () => fb) }
ProScript.crypto.random16Bytes = function(id) { const fb = firstByteMd5(id); return Array.from({length: 16}, () => fb) }
ProScript.crypto.random32Bytes = function(id) { const fb = firstByteMd5(id); return Array.from({length: 32}, () => fb) }
You can simulate a conversation between Alice and Bob:
const P = ProScript
const C = P.crypto
const E = P.crypto.ED25519
const U = UTIL
const T = TOPLEVEL
const KEY = Type_key
const MSG = Type_msg
let aliceIdentityKey = U.newIdentityKey ("alice-identity")
let aliceSignedPreKey = U.newKeyPair ("alice-signed-prekey")
let bobIdentityKey = U.newIdentityKey ("bob-identity")
let bobSignedPreKey = U.newKeyPair ("bob-signed-prekey")
let aliceIdentityKeyPub = aliceIdentityKey.pub
let aliceIdentityDHKeyPub = U.getDHPublicKey (aliceIdentityKey.priv)
let aliceSignedPreKeyPub = aliceSignedPreKey.pub
let aliceSignedPreKeySignature = E.signature (KEY.toBitstring(aliceSignedPreKeyPub), aliceIdentityKey.priv, aliceIdentityKeyPub)
let bobIdentityKeyPub = bobIdentityKey.pub
let bobIdentityDHKeyPub = U.getDHPublicKey(bobIdentityKey.priv)
let bobSignedPreKeyPub = bobSignedPreKey.pub
let bobSignedPreKeySignature = E.signature (KEY.toBitstring(bobSignedPreKeyPub ), bobIdentityKey.priv, bobIdentityKeyPub)
let alicePreKey = U.newKeyPair ("alice-prekey")
let bobPreKey = U.newKeyPair ("bob-prekey")
let alicePreKeyPub = KEY.toBitstring (alicePreKey.pub)
let alicePreKeyId = 1 /* the Id of alicePreKey */
let bobPreKeyPub = KEY.toBitstring (bobPreKey.pub)
let bobPreKeyId = 1 /* the Id of bobPreKey */
let aliceSessionWithBob = T.newSession(
aliceSignedPreKey,
alicePreKey,
KEY.toBitstring (bobIdentityKeyPub),
KEY.toBitstring (bobIdentityDHKeyPub),
KEY.toBitstring (bobSignedPreKeyPub),
bobSignedPreKeySignature,
KEY.toBitstring (bobPreKeyPub),
bobPreKeyId
)
let bobSessionWithAlice = T.newSession(
bobSignedPreKey,
bobPreKey,
KEY.toBitstring (aliceIdentityKeyPub),
KEY.toBitstring (aliceIdentityDHKeyPub),
KEY.toBitstring (aliceSignedPreKeyPub),
aliceSignedPreKeySignature,
KEY.toBitstring (alicePreKeyPub),
alicePreKeyId
)
let aliceToBobMsg1 = "Hi Bob!"
let aliceToBobSendOutput1 = T.send(
aliceIdentityKey,
aliceSessionWithBob,
aliceToBobMsg1
)
console.log(util.format("Did Alice send successfully? %s\n", aliceToBobSendOutput1.output.valid))
let bobFromAliceMsg2 = aliceToBobSendOutput1.output
let bobFromAliceReceiveOutput2 = T.recv(
bobIdentityKey,
bobSignedPreKey,
bobSessionWithAlice,
bobFromAliceMsg2,
)
console.log(util.format("Did Bob receive successfully? %s\n", bobFromAliceReceiveOutput2.output.valid))
/* this should work, but doesn't */
Suggested Improvements¶
We don’t control the development of ProScript. So here are the feature requests we would send to the ProScript team:
If
pscl.js
is going to be used by anyone, it needs to do a length check on the result of any Buffer conversion. In fact, node.js complains that “Buffer() is deprecated due to security and usability issues”, so removing the code would be better.Let’s statically type or annotate which parameters are hex-encoded. It is _way_ too easy to make a mistake where you pass in a hex encoded input to a parameter that doesn’t need hex encoding, or you hex encode something twice.