Skip to content

Commit f289d4e

Browse files
committed
chore: wip
1 parent 9b8fec5 commit f289d4e

9 files changed

Lines changed: 473 additions & 130 deletions

File tree

.vscode/dictionary.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ httx
2828
iconify
2929
imgx
3030
ipadding
31+
jsbn
3132
keychain
3233
Keychains
3334
Keypair

src/asn1.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,13 @@ interface ExtendedError extends Error {
145145
integer?: number
146146
}
147147

148-
interface Asn1Object {
148+
export interface Asn1Object {
149+
name: string
149150
tagClass: number
150151
type: number
151152
constructed: boolean
152-
composed: boolean
153+
composed?: boolean
154+
captureAsn1: string
153155
value: any
154156
bitStringContents?: string
155157
original?: Asn1Object

src/jsbn.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export class BigInteger {
119119
return c
120120
}
121121

122-
private fromNumber(value: number, length: number, randomizer?: IPRNG | number): void {
122+
public fromNumber(value: number, length: number, randomizer?: IPRNG | number): void {
123123
if (typeof randomizer === 'number') {
124124
// New BigInteger(int, int, RNG)
125125
if (value < 2) {
@@ -157,7 +157,7 @@ export class BigInteger {
157157
}
158158
}
159159

160-
private fromInt(value: number): void {
160+
public fromInt(value: number): void {
161161
this.t = 1
162162
this.s = (value < 0) ? -1 : 0
163163
if (value > 0) {
@@ -171,7 +171,7 @@ export class BigInteger {
171171
}
172172
}
173173

174-
private fromString(s: string | number[], b: number): void {
174+
public fromString(s: string | number[], b: number): void {
175175
let k: number
176176
if (b === 16) {
177177
k = 4

src/pkcs1.ts

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
/**
2+
* Partial implementation of PKCS#1 v2.2: RSA-OEAP
3+
*
4+
* Modified but based on the following MIT and BSD licensed code:
5+
*
6+
* https://github.com/kjur/jsjws/blob/master/rsa.js:
7+
*
8+
* The 'jsjws'(JSON Web Signature JavaScript Library) License
9+
*
10+
* Copyright (c) 2012 Kenji Urushima
11+
*
12+
* Permission is hereby granted, free of charge, to any person obtaining a copy
13+
* of this software and associated documentation files (the "Software"), to deal
14+
* in the Software without restriction, including without limitation the rights
15+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16+
* copies of the Software, and to permit persons to whom the Software is
17+
* furnished to do so, subject to the following conditions:
18+
*
19+
* The above copyright notice and this permission notice shall be included in
20+
* all copies or substantial portions of the Software.
21+
*
22+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28+
* THE SOFTWARE.
29+
*
30+
* http://webrsa.cvs.sourceforge.net/viewvc/webrsa/Client/RSAES-OAEP.js?content-type=text%2Fplain:
31+
*
32+
* RSAES-OAEP.js
33+
* $Id: RSAES-OAEP.js,v 1.1.1.1 2003/03/19 15:37:20 ellispritchard Exp $
34+
* JavaScript Implementation of PKCS #1 v2.1 RSA CRYPTOGRAPHY STANDARD (RSA Laboratories, June 14, 2002)
35+
* Copyright (C) Ellis Pritchard, Guardian Unlimited 2003.
36+
* Contact: ellis@nukinetics.com
37+
* Distributed under the BSD License.
38+
*
39+
* Official documentation: http://www.rsa.com/rsalabs/node.asp?id=2125
40+
*
41+
* @author Evan Jones (http://evanjones.ca/)
42+
* @author Dave Longley
43+
*
44+
* Copyright (c) 2013-2014 Digital Bazaar, Inc.
45+
*/
46+
47+
import { sha1 } from './sha1'
48+
/**
49+
* Encode the given RSAES-OAEP message (M) using key, with optional label (L)
50+
* and seed.
51+
*
52+
* This method does not perform RSA encryption, it only encodes the message
53+
* using RSAES-OAEP.
54+
*
55+
* @param key the RSA key to use.
56+
* @param message the message to encode.
57+
* @param options the options to use:
58+
* label an optional label to use.
59+
* seed the seed to use.
60+
* md the message digest object to use, undefined for SHA-1.
61+
* mgf1 optional mgf1 parameters:
62+
* md the message digest object to use for MGF1.
63+
*
64+
* @return the encoded message bytes.
65+
*/
66+
export function encode_rsa_oaep(key: any, message: any, options: any): string {
67+
// parse arguments
68+
let label;
69+
let seed;
70+
let md;
71+
let mgf1Md;
72+
73+
// legacy args (label, seed, md)
74+
if (typeof options === 'string') {
75+
label = options;
76+
seed = arguments[3] || undefined;
77+
md = arguments[4] || undefined;
78+
} else if (options) {
79+
label = options.label || undefined;
80+
seed = options.seed || undefined;
81+
md = options.md || undefined;
82+
83+
if (options.mgf1 && options.mgf1.md) {
84+
mgf1Md = options.mgf1.md;
85+
}
86+
}
87+
88+
// default OAEP to SHA-1 message digest
89+
if (!md) {
90+
md = sha1.create();
91+
} else {
92+
md.start();
93+
}
94+
95+
// default MGF-1 to same as OAEP
96+
if (!mgf1Md) {
97+
mgf1Md = md;
98+
}
99+
100+
// compute length in bytes and check output
101+
var keyLength = Math.ceil(key.n.bitLength() / 8);
102+
var maxLength = keyLength - 2 * md.digestLength - 2;
103+
if (message.length > maxLength) {
104+
var error = new Error('RSAES-OAEP input message length is too long.');
105+
error.length = message.length;
106+
error.maxLength = maxLength;
107+
throw error;
108+
}
109+
110+
if (!label) {
111+
label = '';
112+
}
113+
md.update(label, 'raw');
114+
var lHash = md.digest();
115+
116+
var PS = '';
117+
var PS_length = maxLength - message.length;
118+
for (var i = 0; i < PS_length; i++) {
119+
PS += '\x00';
120+
}
121+
122+
var DB = lHash.getBytes() + PS + '\x01' + message;
123+
124+
if (!seed) {
125+
seed = forge.random.getBytes(md.digestLength);
126+
} else if (seed.length !== md.digestLength) {
127+
var error = new Error('Invalid RSAES-OAEP seed. The seed length must ' +
128+
'match the digest length.');
129+
error.seedLength = seed.length;
130+
error.digestLength = md.digestLength;
131+
throw error;
132+
}
133+
134+
var dbMask = rsa_mgf1(seed, keyLength - md.digestLength - 1, mgf1Md);
135+
var maskedDB = forge.util.xorBytes(DB, dbMask, DB.length);
136+
137+
var seedMask = rsa_mgf1(maskedDB, md.digestLength, mgf1Md);
138+
var maskedSeed = forge.util.xorBytes(seed, seedMask, seed.length);
139+
140+
// return encoded message
141+
return '\x00' + maskedSeed + maskedDB;
142+
};
143+
144+
/**
145+
* Decode the given RSAES-OAEP encoded message (EM) using key, with optional
146+
* label (L).
147+
*
148+
* This method does not perform RSA decryption, it only decodes the message
149+
* using RSAES-OAEP.
150+
*
151+
* @param key the RSA key to use.
152+
* @param em the encoded message to decode.
153+
* @param options the options to use:
154+
* label an optional label to use.
155+
* md the message digest object to use for OAEP, undefined for SHA-1.
156+
* mgf1 optional mgf1 parameters:
157+
* md the message digest object to use for MGF1.
158+
*
159+
* @return the decoded message bytes.
160+
*/
161+
pkcs1.decode_rsa_oaep = function (key, em, options) {
162+
// parse args
163+
var label;
164+
var md;
165+
var mgf1Md;
166+
// legacy args
167+
if (typeof options === 'string') {
168+
label = options;
169+
md = arguments[3] || undefined;
170+
} else if (options) {
171+
label = options.label || undefined;
172+
md = options.md || undefined;
173+
if (options.mgf1 && options.mgf1.md) {
174+
mgf1Md = options.mgf1.md;
175+
}
176+
}
177+
178+
// compute length in bytes
179+
var keyLength = Math.ceil(key.n.bitLength() / 8);
180+
181+
if (em.length !== keyLength) {
182+
var error = new Error('RSAES-OAEP encoded message length is invalid.');
183+
error.length = em.length;
184+
error.expectedLength = keyLength;
185+
throw error;
186+
}
187+
188+
// default OAEP to SHA-1 message digest
189+
if (md === undefined) {
190+
md = sha1.create();
191+
} else {
192+
md.start();
193+
}
194+
195+
// default MGF-1 to same as OAEP
196+
if (!mgf1Md) {
197+
mgf1Md = md;
198+
}
199+
200+
if (keyLength < 2 * md.digestLength + 2) {
201+
throw new Error('RSAES-OAEP key is too short for the hash function.');
202+
}
203+
204+
if (!label) {
205+
label = '';
206+
}
207+
md.update(label, 'raw');
208+
var lHash = md.digest().getBytes();
209+
210+
// split the message into its parts
211+
var y = em.charAt(0);
212+
var maskedSeed = em.substring(1, md.digestLength + 1);
213+
var maskedDB = em.substring(1 + md.digestLength);
214+
215+
var seedMask = rsa_mgf1(maskedDB, md.digestLength, mgf1Md);
216+
var seed = forge.util.xorBytes(maskedSeed, seedMask, maskedSeed.length);
217+
218+
var dbMask = rsa_mgf1(seed, keyLength - md.digestLength - 1, mgf1Md);
219+
var db = forge.util.xorBytes(maskedDB, dbMask, maskedDB.length);
220+
221+
var lHashPrime = db.substring(0, md.digestLength);
222+
223+
// constant time check that all values match what is expected
224+
var error = (y !== '\x00');
225+
226+
// constant time check lHash vs lHashPrime
227+
for (var i = 0; i < md.digestLength; ++i) {
228+
error |= (lHash.charAt(i) !== lHashPrime.charAt(i));
229+
}
230+
231+
// "constant time" find the 0x1 byte separating the padding (zeros) from the
232+
// message
233+
// TODO: It must be possible to do this in a better/smarter way?
234+
var in_ps = 1;
235+
var index = md.digestLength;
236+
for (var j = md.digestLength; j < db.length; j++) {
237+
var code = db.charCodeAt(j);
238+
239+
var is_0 = (code & 0x1) ^ 0x1;
240+
241+
// non-zero if not 0 or 1 in the ps section
242+
var error_mask = in_ps ? 0xfffe : 0x0000;
243+
error |= (code & error_mask);
244+
245+
// latch in_ps to zero after we find 0x1
246+
in_ps = in_ps & is_0;
247+
index += in_ps;
248+
}
249+
250+
if (error || db.charCodeAt(index) !== 0x1) {
251+
throw new Error('Invalid RSAES-OAEP padding.');
252+
}
253+
254+
return db.substring(index + 1);
255+
};
256+
257+
function rsa_mgf1(seed, maskLength, hash) {
258+
// default to SHA-1 message digest
259+
if (!hash) {
260+
hash = sha1.create();
261+
}
262+
var t = '';
263+
var count = Math.ceil(maskLength / hash.digestLength);
264+
for (var i = 0; i < count; ++i) {
265+
var c = String.fromCharCode(
266+
(i >> 24) & 0xFF, (i >> 16) & 0xFF, (i >> 8) & 0xFF, i & 0xFF);
267+
hash.start();
268+
hash.update(seed + c);
269+
t += hash.digest().getBytes();
270+
}
271+
return t.substring(0, maskLength);
272+
}
273+
274+
export const pkcs1 = {
275+
encode_rsa_oaep,
276+
decode_rsa_oaep,
277+
rsa_mgf1,
278+
}

src/pki.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99
import { asn1 } from './asn1'
1010
import { pem } from './pem'
11+
import { privateKeyToAsn1, privateKeyFromAsn1 } from './rsa'
1112

1213
/**
1314
* Converts an RSA private key from PEM format.
@@ -16,7 +17,7 @@ import { pem } from './pem'
1617
*
1718
* @return the private key.
1819
*/
19-
export function privateKeyFromPem(pem: string) {
20+
export function privateKeyFromPem(pem: string): any {
2021
const msg = pem.decode(pem)[0]
2122

2223
if (msg.type !== 'PRIVATE KEY' && msg.type !== 'RSA PRIVATE KEY') {
@@ -32,7 +33,7 @@ export function privateKeyFromPem(pem: string) {
3233
// convert DER to ASN.1 object
3334
const obj = asn1.fromDer(msg.body)
3435

35-
return pki.privateKeyFromAsn1(obj)
36+
return privateKeyFromAsn1(obj)
3637
}
3738

3839
/**

src/random.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@ export function getRandomValues(arr: Uint32Array): Uint32Array {
214214
// Expose PRNG spawning capability
215215
export const createInstance: () => PRNG = spawnPrng
216216

217+
export function getBytes(count: number): string | void {
218+
return createInstance().getBytes(count)
219+
}
220+
217221
export function getBytesSync(count: number): string {
218222
return createInstance().getBytesSync(count)
219223
}

0 commit comments

Comments
 (0)