Skip to content

Commit 692c627

Browse files
committed
chore: wip
chore: wip
1 parent dee84ab commit 692c627

6 files changed

Lines changed: 325 additions & 75 deletions

File tree

.vscode/dictionary.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ destructurable
2323
diversifier
2424
dnsx
2525
dtsx
26+
EMSA
2627
Encs
2728
entrypoints
2829
epki
@@ -62,10 +63,12 @@ postcompile
6263
prefetch
6364
preinstall
6465
primeinc
66+
pssobj
6567
quickfix
6668
rootca
6769
RSAES
6870
rsapss
71+
RSASSA
6972
scalarbase
7073
scalarmult
7174
shikijs

src/mgf.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
* @author Chris Breuer
66
*/
77

8-
import { mgf1 } from './mgf1'
98
import { sha1 } from './algorithms/hash/sha1'
9+
import { mgf1 } from './mgf1'
1010

1111
export interface MGF {
1212
mgf1: (seed: string, maskLen: number) => string
1313
}
1414

1515
export const mgf: MGF = {
16-
mgf1: (seed: string, maskLen: number) => mgf1.create(sha1.create()).generate(seed, maskLen)
16+
mgf1: (seed: string, maskLen: number) => mgf1.create(sha1.create()).generate(seed, maskLen),
1717
}

src/mgf1.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
* @author Chris Breuer
77
*/
88

9-
import { ByteBuffer, ByteStringBuffer } from './utils'
9+
import type { ByteStringBuffer } from './utils'
10+
import { ByteBuffer } from './utils'
1011

1112
interface MessageDigest {
1213
start: () => void
@@ -54,7 +55,7 @@ export function create(md: MessageDigest) {
5455
}
5556

5657
return {
57-
generate: generate
58+
generate,
5859
} as { generate: (seed: string, maskLen: number) => string }
5960
}
6061

src/pss.ts

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
/**
2+
* Javascript implementation of PKCS#1 PSS signature padding.
3+
*
4+
* @author Stefan Siegl
5+
* @author Chris Breuer
6+
*/
7+
import { random } from './utils'
8+
import { util } from './utils'
9+
import { ByteStringBuffer } from './utils'
10+
11+
interface MessageDigest {
12+
digestLength: number
13+
start: () => void
14+
update: (data: string) => void
15+
digest: () => ByteStringBuffer
16+
}
17+
18+
interface MaskGenerationFunction {
19+
generate: (seed: string, maskLen: number) => string
20+
}
21+
22+
interface PRNG {
23+
getBytesSync: (count: number) => string
24+
}
25+
26+
/**
27+
* Creates a PSS signature scheme object.
28+
*
29+
* There are several ways to provide a salt for encoding:
30+
*
31+
* 1. Specify the saltLength only and the built-in PRNG will generate it.
32+
* 2. Specify the saltLength and a custom PRNG with 'getBytesSync' defined that will be used.
33+
* 3. Specify the salt itself as a forge.util.ByteBuffer.
34+
*
35+
* @param options the options to use:
36+
* @param options.md the message digest object to use, a forge md instance.
37+
* @param options.mgf the mask generation function to use, a forge mgf instance.
38+
* @param options.saltLength the length of the salt in octets.
39+
* @param options.prng the pseudo-random number generator to use to produce a salt.
40+
* @param options.salt the salt to use when encoding.
41+
*
42+
* @return a signature scheme object.
43+
*/
44+
export function createPSS(options: {
45+
md: MessageDigest
46+
mgf: MaskGenerationFunction
47+
saltLength: number
48+
prng?: PRNG
49+
salt?: string | ByteStringBuffer
50+
}): {
51+
encode: (md: MessageDigest, modBits: number) => string
52+
verify: (mHash: string, em: string, modBits: number) => boolean
53+
} {
54+
// backwards compatibility w/legacy args: hash, mgf, sLen
55+
if (arguments.length === 3) {
56+
options = {
57+
md: arguments[0],
58+
mgf: arguments[1],
59+
saltLength: arguments[2],
60+
}
61+
}
62+
63+
const hash = options.md
64+
const mgf = options.mgf
65+
const hLen = hash.digestLength
66+
67+
let salt_ = options.salt || null
68+
if (typeof salt_ === 'string')
69+
// assume binary-encoded string
70+
salt_ = util.createBuffer(salt_)
71+
72+
let sLen
73+
if ('saltLength' in options)
74+
sLen = options.saltLength
75+
else if (salt_ !== null)
76+
sLen = salt_.length()
77+
else
78+
throw new Error('Salt length not specified or specific salt not given.')
79+
80+
if (salt_ !== null && salt_.length() !== sLen)
81+
throw new Error('Given salt length does not match length of given salt.')
82+
83+
const prng = options.prng || random
84+
85+
const pssobj = {
86+
/**
87+
* Encodes a PSS signature.
88+
*
89+
* This function implements EMSA-PSS-ENCODE as per RFC 3447, section 9.1.1.
90+
*
91+
* @param md the message digest object with the hash to sign.
92+
* @param modBits the length of the RSA modulus in bits.
93+
*
94+
* @return the encoded message as a binary-encoded string of length ceil((modBits - 1) / 8).
95+
*/
96+
encode: (md: MessageDigest, modBits: number) => {
97+
let i
98+
const emBits = modBits - 1
99+
const emLen = Math.ceil(emBits / 8)
100+
101+
// 2. Let mHash = Hash(M), an octet string of length hLen.
102+
const mHash = md.digest().getBytes()
103+
104+
// 3. If emLen < hLen + sLen + 2, output "encoding error" and stop.
105+
if (emLen < hLen + sLen + 2)
106+
throw new Error('Message is too long to encrypt.')
107+
108+
// 4. Generate a random octet string salt of length sLen;
109+
// if sLen = 0,
110+
// then salt is the empty string.
111+
let salt
112+
if (salt_ === null)
113+
salt = prng.getBytesSync(sLen)
114+
else
115+
salt = salt_.bytes()
116+
117+
// 5. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt;
118+
const m_ = new util.ByteBuffer()
119+
m_.fillWithByte(0, 8)
120+
m_.putBytes(mHash)
121+
m_.putBytes(salt)
122+
123+
// 6. Let H = Hash(M'), an octet string of length hLen.
124+
hash.start()
125+
hash.update(m_.getBytes())
126+
const h = hash.digest().getBytes()
127+
128+
// 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2
129+
// zero octets. The length of PS may be 0.
130+
const ps = new util.ByteBuffer()
131+
ps.fillWithByte(0, emLen - sLen - hLen - 2)
132+
133+
// 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
134+
// emLen - hLen - 1.
135+
ps.putByte(0x01)
136+
ps.putBytes(salt)
137+
const db = ps.getBytes()
138+
139+
// 9. Let dbMask = MGF(H, emLen - hLen - 1).
140+
const maskLen = emLen - hLen - 1
141+
const dbMask = mgf.generate(h, maskLen)
142+
143+
// 10. Let maskedDB = DB \xor dbMask.
144+
let maskedDB = ''
145+
for (i = 0; i < maskLen; i++)
146+
maskedDB += String.fromCharCode(db.charCodeAt(i) ^ dbMask.charCodeAt(i))
147+
148+
// 11. Set the leftmost 8emLen - emBits bits of the leftmost octet in
149+
// maskedDB to zero.
150+
const mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF
151+
maskedDB = String.fromCharCode(maskedDB.charCodeAt(0) & ~mask)
152+
+ maskedDB.substr(1)
153+
154+
// 12. Let EM = maskedDB || H || 0xbc.
155+
// 13. Output EM.
156+
return maskedDB + h + String.fromCharCode(0xBC)
157+
},
158+
159+
/**
160+
* Verifies a PSS signature.
161+
*
162+
* This function implements EMSA-PSS-VERIFY as per RFC 3447, section 9.1.2.
163+
*
164+
* @param mHash the message digest hash, as a binary-encoded string, to compare against the signature.
165+
* @param em the encoded message, as a binary-encoded string (RSA decryption result).
166+
* @param modsBits the length of the RSA modulus in bits.
167+
*
168+
* @return true if the signature was verified, false if not.
169+
*/
170+
verify: (mHash: string, em: string, modBits: number) => {
171+
let i
172+
const emBits = modBits - 1
173+
const emLen = Math.ceil(emBits / 8)
174+
175+
// c. Convert the message representative m to an encoded message EM
176+
// of length emLen = ceil((modBits - 1) / 8) octets, where modBits
177+
// is the length in bits of the RSA modulus n
178+
em = em.substr(-emLen)
179+
180+
// 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop.
181+
if (emLen < hLen + sLen + 2)
182+
throw new Error('Inconsistent parameters to PSS signature verification.')
183+
184+
// 4. If the rightmost octet of EM does not have hexadecimal value
185+
// 0xbc, output "inconsistent" and stop.
186+
if (em.charCodeAt(emLen - 1) !== 0xBC)
187+
throw new Error('Encoded message does not end in 0xBC.')
188+
189+
190+
// 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and
191+
// let H be the next hLen octets.
192+
const maskLen = emLen - hLen - 1
193+
const maskedDB = em.substr(0, maskLen)
194+
const h = em.substr(maskLen, hLen)
195+
196+
// 6. If the leftmost 8emLen - emBits bits of the leftmost octet in
197+
// maskedDB are not all equal to zero, output "inconsistent" and stop.
198+
const mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF
199+
if ((maskedDB.charCodeAt(0) & mask) !== 0)
200+
throw new Error('Bits beyond keysize not zero as expected.')
201+
202+
// 7. Let dbMask = MGF(H, emLen - hLen - 1).
203+
const dbMask = mgf.generate(h, maskLen)
204+
205+
// 8. Let DB = maskedDB \xor dbMask.
206+
let db = ''
207+
for (i = 0; i < maskLen; i++)
208+
db += String.fromCharCode(maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i))
209+
210+
// 9. Set the leftmost 8emLen - emBits bits of the leftmost octet
211+
// in DB to zero.
212+
db = String.fromCharCode(db.charCodeAt(0) & ~mask) + db.substr(1)
213+
214+
// 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero
215+
// or if the octet at position emLen - hLen - sLen - 1 (the leftmost
216+
// position is "position 1") does not have hexadecimal value 0x01,
217+
// output "inconsistent" and stop.
218+
const checkLen = emLen - hLen - sLen - 2
219+
for (i = 0; i < checkLen; i++)
220+
if (db.charCodeAt(i) !== 0x00)
221+
throw new Error('Leftmost octets not zero as expected')
222+
223+
if (db.charCodeAt(checkLen) !== 0x01)
224+
throw new Error('Inconsistent PSS signature, 0x01 marker not found')
225+
226+
// 11. Let salt be the last sLen octets of DB.
227+
const salt = db.substr(-sLen)
228+
229+
// 12. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt
230+
const m_ = new util.ByteBuffer()
231+
m_.fillWithByte(0, 8)
232+
m_.putBytes(mHash)
233+
m_.putBytes(salt)
234+
235+
// 13. Let H' = Hash(M'), an octet string of length hLen.
236+
hash.start()
237+
hash.update(m_.getBytes())
238+
const h_ = hash.digest().getBytes()
239+
240+
// 14. If H = H', output "consistent." Otherwise, output "inconsistent."
241+
return h === h_
242+
}
243+
}
244+
245+
return pssobj
246+
}

src/utils/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,7 @@ export interface Util {
10451045
createBuffer: typeof createBuffer
10461046
_checkBitsParam: typeof _checkBitsParam
10471047
xorBytes: typeof xorBytes
1048+
ByteBuffer: typeof ByteBuffer
10481049
}
10491050

10501051
export const util: Util = {
@@ -1073,6 +1074,7 @@ export const util: Util = {
10731074
createBuffer,
10741075
_checkBitsParam,
10751076
xorBytes,
1077+
ByteBuffer,
10761078
}
10771079

10781080
export default util

0 commit comments

Comments
 (0)