Browse Source

v0.0.3: ecdsa is soooooo close

tags/v1.0.0
AJ ONeal 1 year ago
parent
commit
d928c2bdc0
5 changed files with 438 additions and 120 deletions
  1. 1
    0
      README.md
  2. 13
    0
      convert-from-hex.js
  3. 1
    1
      package.json
  4. 132
    21
      pubkey.js
  5. 291
    98
      re-sign.js

+ 1
- 0
README.md View File

@@ -4,6 +4,7 @@ This is my project for the weekend. I expect to be finished today (Monday Nov 12
4 4
 * 2018-10-10 (Saturday) work has begun
5 5
 * 2018-10-11 (Sunday) W00T! got a CSR generated for RSA with VanillaJS ArrayBuffer
6 6
 * 2018-10-12 (Monday) Figuring out ECDSA CSRs right now
7
+* 2018-10-15 (Thursday) ECDSA is a trixy hobbit... but I think I've got it...
7 8
 
8 9
 <!--
9 10
 Keypairs&trade; for node.js

+ 13
- 0
convert-from-hex.js View File

@@ -0,0 +1,13 @@
1
+'use strict';
2
+
3
+var fs = require('fs');
4
+var path = require('path');
5
+
6
+function convert(name) {
7
+  var ext = path.extname(name);
8
+  var csr = fs.readFileSync(name, 'ascii').replace(/\s\+/g, '');
9
+  var bin = Buffer.from(csr, 'hex');
10
+  fs.writeFileSync(name.replace(new RegExp('\\' + ext + '$'), '') + '.bin', bin);
11
+}
12
+
13
+convert(process.argv[2]);

+ 1
- 1
package.json View File

@@ -1,6 +1,6 @@
1 1
 {
2 2
   "name": "keypairs",
3
-  "version": "0.0.2",
3
+  "version": "0.0.3",
4 4
   "description": "Interchangeably use RSA & ECDSA with PEM and JWK for Signing, Verifying, CSR generation and JOSE. Ugh... that was a mouthful.",
5 5
   "main": "index.js",
6 6
   "scripts": {

+ 132
- 21
pubkey.js View File

@@ -1,21 +1,23 @@
1 1
 (function (exports) {
2 2
 'use strict';
3 3
 
4
+// 1.2.840.113549.1.1.1
5
+// rsaEncryption (PKCS #1)
6
+var OBJ_ID_RSA = '06 09 2A864886F70D010101'.replace(/\s+/g, '').toLowerCase();
7
+// 1.2.840.10045.3.1.7
8
+// prime256v1 (ANSI X9.62 named elliptic curve)
9
+var OBJ_ID_EC  = '06 08 2A8648CE3D030107'.replace(/\s+/g, '').toLowerCase();
10
+
4 11
 // 30 sequence
5 12
 // 03 bit string
6 13
 // 05 null
7 14
 // 06 object id
8 15
 
9
-//       00                                                         00              00          00
10
-// 30 82 01 22 30 0D 06 09  2A 86 48 86 F7 0D 01 01  01 05 00 03 82 01 0F 00  30 82 01 0A 02 82 01 01
11
-// 00 ... 02 03 01 00 01
12
-// 30 82 02 22 30 0D 06 09  2A 86 48 86 F7 0D 01 01  01 05 00 03 82 02 0F 00  30 82 02 0A 02 82 02 01
13
-// 00 ... 02 03 01 00 01
14
-
15 16
 function parsePem(pem) {
16 17
   var typ;
17 18
   var pub;
18
-  var der = fromBase64(pem.toString('ascii').split(/\n/).filter(function (line, i) {
19
+  var crv;
20
+  var der = fromBase64(pem.split(/\n/).filter(function (line, i) {
19 21
     if (0 === i) {
20 22
       if (/ PUBLIC /.test(line)) {
21 23
         pub = true;
@@ -31,18 +33,21 @@ function parsePem(pem) {
31 33
     return !/---/.test(line);
32 34
   }).join(''));
33 35
 
34
-  if (!typ) {
35
-    if (pub) {
36
-      // This is the RSA object ID
37
-      if ('06092A864886F70D010101'.toLowerCase() === der.slice(6, 6 + 11).toString('hex')) {
38
-        typ = 'RSA';
39
-      }
36
+  if (!typ || 'EC' === typ) {
37
+    var hex = toHex(der).toLowerCase();
38
+    // This is the RSA object ID
39
+    if (-1 !== hex.indexOf(OBJ_ID_RSA)) {
40
+      typ = 'RSA';
41
+    } else if (-1 !== hex.indexOf(OBJ_ID_EC)) {
42
+      typ = 'EC';
43
+      crv = 'P-256';
40 44
     } else {
41
-      // TODO
45
+      // TODO more than just P-256
46
+      console.warn("unsupported ec curve");
42 47
     }
43 48
   }
44 49
 
45
-  return { typ: typ, pub: pub, der: der };
50
+  return { typ: typ, pub: pub, der: der, crv: crv };
46 51
 }
47 52
 
48 53
 function toHex(ab) {
@@ -62,6 +67,60 @@ function toHex(ab) {
62 67
   return hex.join('');
63 68
 }
64 69
 
70
+function fromHex(hex) {
71
+  if ('undefined' !== typeof Buffer) {
72
+    return Buffer.from(hex, 'hex');
73
+  }
74
+  var ab = new ArrayBuffer(hex.length/2);
75
+  var i;
76
+  var j;
77
+  ab = new Uint8Array(ab);
78
+  for (i = 0, j = 0; i < (hex.length/2); i += 1) {
79
+    ab[i] = parseInt(hex.slice(j, j+1), 16);
80
+    j += 2;
81
+  }
82
+  return ab.buffer;
83
+}
84
+
85
+function readEcPrivkey(der) {
86
+  return readEcPubkey(der);
87
+}
88
+
89
+function readEcPubkey(der) {
90
+  // the key is the last 520 bits of both the private key and the public key
91
+  // he 3 bits prior identify the key as
92
+  var x, y;
93
+  var compressed;
94
+  var keylen = 32;
95
+  var offset = 64;
96
+  var headerSize = 4;
97
+  var header = toHex(der.slice(der.byteLength - (offset + headerSize), der.byteLength - offset));
98
+
99
+  if ('03420004' !== header) {
100
+    offset = 32;
101
+    header = toHex(der.slice(der.byteLength - (offset + headerSize), der.byteLength - offset));
102
+    if ('03420002' !== header) {
103
+      throw new Error("not a valid EC P-256 key (expected 0x0342004 or 0x0342002 as pub key preamble, but found " + header + ")");
104
+    }
105
+  }
106
+  console.log('header', header);
107
+  console.log('offset', offset);
108
+
109
+  // The one good thing that came from the b***kchain hysteria: good EC documentation
110
+  // https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/
111
+  compressed = ('2' === header[header.byteLength -1]);
112
+  console.log(der.byteLength - offset, (der.byteLength - offset) + keylen);
113
+  x = der.slice(der.byteLength - offset, (der.byteLength - offset) + keylen);
114
+  if (!compressed) {
115
+    y = der.slice(der.byteLength - keylen, der.byteLength);
116
+  }
117
+
118
+  return {
119
+    x: x
120
+  , y: y || null
121
+  };
122
+}
123
+
65 124
 function readPubkey(der) {
66 125
   var offset = 28 + 5; // header plus size
67 126
   var ksBytes = der.slice(30, 32);
@@ -122,23 +181,70 @@ function toRsaPub(pub) {
122 181
   return der.buffer;
123 182
 }
124 183
 
125
-function formatAsPem(str, privacy, pemName) {
126
-  var pemstr = (pemName ? pemName + ' ' : '');
127
-  var privstr = (privacy ? privacy + ' ' : '');
128
-  var finalString = '-----BEGIN ' + pemstr + privstr + 'KEY-----\n';
184
+function h(d) {
185
+  d = d.toString(16);
186
+  if (d.length % 2) {
187
+    return '0' + d;
188
+  }
189
+  return d;
190
+}
191
+
192
+// I used OpenSSL to create EC keys with the P-256 curve TODO P-384.
193
+// Then I used https://lapo.it/asn1js/ to see which bits changed.
194
+// And I created a template from the bits that do and don't.
195
+// No ASN.1 and X.509 parsers or generators. Yay!
196
+var ecP256Asn1Head = (
197
+  '30 {n}'                              // 0x59 = 89 bytes  // sequence
198
+  + '30 _13'                            // 0x13 = 19 bytes  // sequence
199
+    + '06 _07 2A 86 48 CE 3D 02 01'     // 0x07 = 7 bytes   // 1.2.840.10045.2.1 ecPublicKey (ANSI X9.62 public key type)
200
+    + '06 _08 2A 86 48 CE 3D 03 01 07'  // 0x08 = 8 bytes   // 1.2.840.10045.3.1.7 prime256v1 (ANSI X9.62 named elliptic curve)
201
+  + '03 {p} 00 {c} {x} {y}'             // xlen+ylen+1+1    // bit string
202
+  ).replace(/[\s_]+/g, '')
203
+;
204
+function toEcPub(x, y) {
205
+  // 256 // 2048-bit
206
+  var keylen = toHex([ x.byteLength + (y && y.byteLength || 0) ]);
207
+  var head = ecP256Asn1Head
208
+    .replace(/{n}/, h(0x13 + 2 + 2 + keylen))
209
+    .replace(/{p}/, h(2 + keylen))
210
+    .replace(/{c}/, y && '04' || '02')
211
+    .replace(/{x}/, toHex(x))
212
+    .replace(/{y}/, y && toHex(y) || '')
213
+  ;
214
+  return fromHex(head);
215
+}
216
+
217
+function formatAsPem(str) {
218
+  var finalString = '';
129 219
 
130 220
   while (str.length > 0) {
131 221
       finalString += str.substring(0, 64) + '\n';
132 222
       str = str.substring(64);
133 223
   }
224
+  return finalString;
225
+}
134 226
 
135
-  finalString = finalString + '-----END ' + pemstr + privstr + 'KEY-----';
227
+function formatAsPrivatePem(str, privacy, pemName) {
228
+  var pemstr = (pemName ? pemName + ' ' : '');
229
+  var privstr = (privacy ? privacy + ' ' : '');
230
+  var finalString = '-----BEGIN ' + pemstr + privstr + 'KEY-----\n';
231
+  finalString += formatAsPem(str);
232
+  finalString += '-----END ' + pemstr + privstr + 'KEY-----';
136 233
 
137 234
   return finalString;
138 235
 }
139 236
 
140 237
 function formatAsPublicPem(str) {
141
-  return formatAsPem(str, 'PUBLIC', '');
238
+  var privacy = 'PUBLIC';
239
+  var pemName = '';
240
+  var pemstr = (pemName ? pemName + ' ' : '');
241
+  var privstr = (privacy ? privacy + ' ' : '');
242
+
243
+  var finalString = '-----BEGIN ' + pemstr + privstr + 'KEY-----\n';
244
+  finalString += formatAsPem(str);
245
+  finalString += '-----END ' + pemstr + privstr + 'KEY-----';
246
+
247
+  return finalString;
142 248
 }
143 249
 
144 250
 function toBase64(der) {
@@ -172,10 +278,15 @@ function fromBase64(b64) {
172 278
 exports.parsePem = parsePem;
173 279
 exports.toBase64 = toBase64;
174 280
 exports.toRsaPub = toRsaPub;
281
+exports.toEcPub = toEcPub;
175 282
 exports.formatAsPublicPem = formatAsPublicPem;
283
+exports.formatAsPrivatePem = formatAsPrivatePem;
176 284
 exports.formatAsPem = formatAsPem;
177 285
 exports.readPubkey = readPubkey;
286
+exports.readEcPubkey = readEcPubkey;
287
+exports.readEcPrivkey = readEcPrivkey;
178 288
 exports.readPrivkey = readPrivkey;
179 289
 exports.toHex = toHex;
290
+exports.fromHex = fromHex;
180 291
 
181 292
 }('undefined' !== typeof module ? module.exports: window));

+ 291
- 98
re-sign.js View File

@@ -4,22 +4,27 @@ var crypto = require('crypto');
4 4
 var fs = require('fs');
5 5
 var pubkey = require('./pubkey.js');
6 6
 
7
-var keyname = process.argv[2];
8
-var dername = process.argv[3];
9
-
10
-var keypem = fs.readFileSync(keyname);
11
-var csrFull = fs.readFileSync(dername);
12
-var csrFull = csrFull.buffer.slice(csrFull.byteOffset, csrFull.byteOffset + csrFull.byteLength);
13
-
14 7
 // these are static ASN.1 segments
15 8
 // The head specifies that there will be 3 segments and a content length
16 9
 // (those segments will be content, signature header, and signature)
17 10
 var csrHead = '30 82 {0seq0len}'.replace(/\s+/g, '');
18 11
 // The tail specifies the RSA256 signature header (and is followed by the signature
19 12
 var csrRsaFoot =
20
-  ( '30 0D 06 09 2A 86 48 86 F7 0D 01 01 0B'
13
+  ( '30 0D'
14
+    // 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
15
+  + '06 09 2A 86 48 86 F7 0D 01 01 0B'
21 16
   + '05 00'
22
-  + '03 82 01 01 00'
17
+  + '03 82 01 01 00' // preamble to sig
18
+  ).replace(/\s+/g, '');
19
+var csrEcFoot =
20
+  ( '30 0A'
21
+    // 1.2.840.10045.4.3.2 ecdsaWithSHA256
22
+    // (ANSI X9.62 ECDSA algorithm with SHA256)
23
+  + '06 08 2A 86 48 CE 3D 04 03 02'
24
+  + '03 49 00'
25
+  + '30 46'
26
+  + '02 21 00 {s}' // 32 bytes of sig
27
+  + '02 21 00 {r}' // 32 bytes of sig
23 28
   ).replace(/\s+/g, '');
24 29
 var csrDomains = '82 {dlen} {domain.tld}';  // 2+n bytes (type 82?)
25 30
 /*
@@ -57,19 +62,31 @@ var csrRsaContent =
57 62
 
58 63
 function privateToPub(pem) {
59 64
   var pubbuf;
65
+  var pubxy;
60 66
   var key = pubkey.parsePem(pem);
61
-  if ('RSA' !== key.typ) {
62
-    throw new Error(key.typ + " not supported");
63
-  }
64
-  if (key.pub) {
65
-    pubbuf = pubkey.readPubkey(key.der);
66
-  } else {
67
-    pubbuf = pubkey.readPrivkey(key.der);
67
+  var pubder;
68
+
69
+  if ('RSA' === key.typ) {
70
+    if (key.pub) {
71
+      pubbuf = pubkey.readPubkey(key.der);
72
+    } else {
73
+      pubbuf = pubkey.readPrivkey(key.der);
74
+    }
75
+    pubder = pubkey.toRsaPub(pubbuf);
76
+  } else if ('EC' === key.typ) {
77
+    if (key.crv && 'P-256' !== key.crv) {
78
+      throw new Error("unsupported curve type");
79
+    }
80
+    if (key.pub) {
81
+      pubxy = pubkey.readEcPubkey(key.der);
82
+    } else {
83
+      pubxy = pubkey.readEcPrivkey(key.der);
84
+    }
85
+    pubder = pubkey.toEcPub(pubxy.x, pubxy.y);
68 86
   }
69 87
 
70 88
   //console.log(pubbuf.byteLength, pubkey.toHex(pubbuf));
71
-  var der = pubkey.toRsaPub(pubbuf);
72
-  var b64 = pubkey.toBase64(der);
89
+  var b64 = pubkey.toBase64(pubder);
73 90
   return pubkey.formatAsPublicPem(b64);
74 91
 }
75 92
 
@@ -89,45 +106,6 @@ function pubToPem(pubbuf) {
89 106
   return pubkey.formatAsPublicPem(b64);
90 107
 }
91 108
 
92
-var sigend = (csrFull.byteLength - (2048 / 8));
93
-var sig = csrFull.slice(sigend);
94
-
95
-console.log();
96
-console.log();
97
-console.log('csr (' + csrFull.byteLength + ')');
98
-console.log(pubkey.toHex(csrFull));
99
-console.log();
100
-
101
-// First 4 bytes define Segment, segment length, and content length
102
-console.log(sigend, csrRsaFoot, csrRsaFoot.length/2);
103
-var csrbody = csrFull.slice(4, sigend - (csrRsaFoot.length/2));
104
-console.log('csr body (' + csrbody.byteLength + ')');
105
-console.log(pubkey.toHex(csrbody));
106
-console.log();
107
-
108
-var csrpub = csrFull.slice(63 + 5, 63 + 5 + 256);
109
-console.log('csr pub (' + csrpub.byteLength + ')');
110
-console.log(pubkey.toHex(csrpub));
111
-console.log();
112
-
113
-console.log('sig (' + sig.byteLength + ')');
114
-console.log(pubkey.toHex(sig));
115
-console.log();
116
-
117
-var csrpem = pubToPem(csrpub);
118
-console.log(csrpem);
119
-console.log();
120
-
121
-var prvpem = privateToPub(keypem);
122
-console.log(prvpem);
123
-console.log();
124
-
125
-if (csrpem === prvpem) {
126
-  console.log("Public Keys Match");
127
-} else {
128
-  throw new Error("public key read from keyfile doesn't match public key read from CSR");
129
-}
130
-
131 109
 function h(d) {
132 110
   d = d.toString(16);
133 111
   if (d.length % 2) {
@@ -163,13 +141,14 @@ function createCsrBodyRsa(domains, csrpub) {
163 141
     .replace(/{[^}]+}/, h(
164 142
         3
165 143
       + 13 + sublen
166
-      + 38 + publen
144
+      + 38 + publen // Length for RSA-related stuff
167 145
       + 30 + sanlen
168 146
     ))
169 147
 
170 148
       // #0 Total 3
171 149
     , '02 01 00'                                                      // 3 bytes, int 0
172 150
 
151
+      // Subject
173 152
       // #1 Total 2+11+n
174 153
     , '30 {3.2.0seqlen}'                                              // 2 bytes, sequence
175 154
       .replace(/{[^}]+}/, h(2+2+5+2+sublen))
@@ -182,6 +161,7 @@ function createCsrBodyRsa(domains, csrpub) {
182 161
           .replace(/{dlen}/, h(sublen))
183 162
           .replace(/{domain\.tld}/, strToHex(domains[0]))
184 163
 
164
+      // Public Key
185 165
       // #2 Total 4+28+n+1+5
186 166
     , '30 82 {8.2.2seqlen}'                                           // 4 bytes, sequence
187 167
       .replace(/{[^}]+}/, h(2+11+2+4+1+4+4+publen+1+5))
@@ -198,6 +178,7 @@ function createCsrBodyRsa(domains, csrpub) {
198 178
         , '02 03 {mod}'                                               // 5 bytes, key and modules [01 00 01]
199 179
           .replace(/{mod}/, '01 00 01')
200 180
 
181
+      // AltNames
201 182
       // #3 Total 2+28+n
202 183
     , 'A0 {16.2.3ellen}'                                              // 2 bytes, ?? [4B]
203 184
       .replace(/{[^}]+}/, h(2+11+2+2+2+5+2+2+sanlen))
@@ -222,25 +203,44 @@ function createCsrBodyRsa(domains, csrpub) {
222 203
   return fromHex(body);
223 204
 }
224 205
 
225
-function createCsrBodyEc(domains, csrpub) {
206
+function createCsrBodyEc(domains, xy) {
226 207
   var altnames = domains.map(function (d) {
227 208
     return csrDomains.replace(/{dlen}/, h(d.length)).replace(/{domain\.tld}/, strToHex(d));
228 209
   }).join('').replace(/\s+/g, '');
229
-  var publen = csrpub.byteLength;
230 210
   var sublen = domains[0].length;
231 211
   var sanlen = (altnames.length/2);
212
+  var publen = xy.x.byteLength;
213
+  var compression = '04';
214
+  var hxy = '';
215
+  // 04 == x+y, 02 == x-only
216
+  if (xy.y) {
217
+    publen += xy.y.byteLength;
218
+  } else {
219
+    // Note: I don't intend to support compression - it isn't used by most
220
+    // libraries and it requir more dependencies for bigint ops to deflate.
221
+    // This is more just a placeholder. It won't work right now anyway
222
+    // because compression requires an exta bit stored (odd vs even), which
223
+    // I haven't learned yet, and I'm not sure if it's allowed at all
224
+    compression = '02';
225
+  }
226
+  hxy += pubkey.toHex(xy.x);
227
+  if (xy.y) {
228
+    hxy += pubkey.toHex(xy.y);
229
+  }
230
+  console.log('hxy:', hxy);
232 231
 
233
-  var body = [ '30 82 {1.1.0seqlen}'                                  // 4 bytes, sequence
232
+  var body = [ '30 81 {+85+n}'                                        // 4 bytes, sequence
234 233
     .replace(/{[^}]+}/, h(
235 234
         3
236 235
       + 13 + sublen
237
-      + 38 + publen
236
+      + 27 + publen // Length for EC-related P-256 stuff
238 237
       + 30 + sanlen
239 238
     ))
240 239
 
241 240
       // #0 Total 3
242 241
     , '02 01 00'                                                      // 3 bytes, int 0
243 242
 
243
+      // Subject
244 244
       // #1 Total 2+11+n
245 245
     , '30 {3.2.0seqlen}'                                              // 2 bytes, sequence
246 246
       .replace(/{[^}]+}/, h(2+2+5+2+sublen))
@@ -253,38 +253,40 @@ function createCsrBodyEc(domains, csrpub) {
253 253
           .replace(/{dlen}/, h(sublen))
254 254
           .replace(/{domain\.tld}/, strToHex(domains[0]))
255 255
 
256
-      // #2 Total 4+28+n+1+5
257
-    , '30 82 {8.2.2seqlen}'                                           // 4 bytes, sequence
258
-      .replace(/{[^}]+}/, h(2+11+2+4+1+4+4+publen+1+5))
259
-      , '30 0D'                                                       // 2 bytes, sequence
260
-        , '06 09 2A 86 48 86 F7 0D 01 01 01'                          // 11 bytes, rsaEncryption (PKCS #1)
261
-        , '05 00'                                                     // 2 bytes, null (why?)
262
-      , '03 82 {12.3.0bslen} 00'                                      // 4+1 bytes, bit string [01 0F]
263
-        .replace(/{[^}]+}/, h(1+4+4+publen+1+5))
264
-        , '30 82 {13.4.0seqlen}'                                      // 4 bytes, sequence
265
-          .replace(/{[^}]+}/, h(4+publen+1+5))
266
-        , '02 82 {klen} 00 {key}'                                     // 4+n bytes, int (RSA Pub Key)
267
-          .replace(/{klen}/, h(publen+1))
268
-          .replace(/{key}/, pubkey.toHex(csrpub))
269
-        , '02 03 {mod}'                                               // 5 bytes, key and modules [01 00 01]
270
-          .replace(/{mod}/, '01 00 01')
271
-
256
+      // P-256 Public Key
257
+      // #2 Total 2+25+xy
258
+    , '30 {+25+xy}'                                                   // 2 bytes, sequence
259
+      .replace(/{[^}]+}/, h(2+9+10+3+1+publen))
260
+      , '30 13'                                                       // 2 bytes, sequence
261
+          // 1.2.840.10045.2.1 ecPublicKey
262
+          // (ANSI X9.62 public key type)
263
+        , '06 07 2A 86 48 CE 3D 02 01'                                // 9 bytes, object id
264
+          // 1.2.840.10045.3.1.7 prime256v1
265
+          // (ANSI X9.62 named elliptic curve)
266
+        , '06 08 2A 86 48 CE 3D 03 01 07'                             // 10 bytes, object id
267
+      , '03 {xylen} 00 {xy}'                                          // 3+1+n bytes
268
+        .replace(/{xylen}/, h(publen+2))
269
+        .replace(/{xy}/, compression + hxy)
270
+
271
+      // Altnames
272 272
       // #3 Total 2+28+n
273
-    , 'A0 {16.2.3ellen}'                                              // 2 bytes, ?? [4B]
273
+    , 'A0 {+28}'                                                      // 2 bytes, ?? [4B]
274 274
       .replace(/{[^}]+}/, h(2+11+2+2+2+5+2+2+sanlen))
275
-      , '30 {17.3.9seqlen}'                                           // 2 bytes, sequence
275
+      , '30 {+26}'                                                    // 2 bytes, sequence
276 276
         .replace(/{[^}]+}/, h(11+2+2+2+5+2+2+sanlen))
277
-        , '06 09 2A 86 48 86 F7 0D 01 09 0E'                          // 11 bytes, object id (extensionRequest (PKCS #9 via CRMF))
278
-          , '31 {19.5.0setlen}'                                       // 2 bytes, set
277
+          // (extensionRequest (PKCS #9 via CRMF))
278
+        , '06 09 2A 86 48 86 F7 0D 01 09 0E'                          // 11 bytes, object id
279
+          , '31 {+13}'                                                // 2 bytes, set
279 280
             .replace(/{[^}]+}/, h(2+2+5+2+2+sanlen))
280
-            , '30 {20.6.0seqlen}'                                     // 2 bytes, sequence
281
+            , '30 {+11}'                                              // 2 bytes, sequence
281 282
               .replace(/{[^}]+}/, h(2+5+2+2+sanlen))
282
-              , '30 {21.7.0seqlen}'                                   // 2 bytes, sequence
283
+              , '30 {+9}'                                             // 2 bytes, sequence
283 284
                 .replace(/{[^}]+}/, h(5+2+2+sanlen))
284
-                , '06 03 55 1D 11'                                    // 5 bytes, object id (subjectAltName (X.509 extension))
285
-                , '04 {23.8.0octlen}'                                 // 2 bytes, octet string
285
+                  // (subjectAltName (X.509 extension))
286
+                , '06 03 55 1D 11'                                    // 5 bytes, object id
287
+                , '04 {+2}'                                           // 2 bytes, octet string
286 288
                   .replace(/{[^}]+}/, h(2+sanlen))
287
-                  , '30 {24.9.0seqlen}'                               // 2 bytes, sequence
289
+                  , '30 {+n}'                                         // 2 bytes, sequence
288 290
                     .replace(/{[^}]+}/, h(sanlen))
289 291
                     , '{altnames}'                                    // n (elements of sequence)
290 292
                       .replace(/{altnames}/, altnames)
@@ -293,7 +295,37 @@ function createCsrBodyEc(domains, csrpub) {
293 295
   return fromHex(body);
294 296
 }
295 297
 
296
-function createCsr(domains, keypem) {
298
+// https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
299
+function signEc(keypem, ab) {
300
+  var sign = crypto.createSign('SHA256');
301
+  sign.write(new Uint8Array(ab));
302
+  sign.end();
303
+
304
+  var sig = sign.sign(keypem);
305
+  console.log("");
306
+  console.log("SIGNING!!");
307
+  console.log('csr body:', pubkey.toHex(new Uint8Array(ab)));
308
+  console.log('ec sig:', pubkey.toHex(sig));
309
+  console.log("");
310
+	sig = new Uint8Array(sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength));
311
+
312
+  var rlen = new Uint8Array(sig)[3];
313
+  var r = sig.slice(4, 4 + rlen);
314
+  var slen = sig.slice[5 + rlen];
315
+  var s = sig.slice(6 + rlen, 6 + rlen + slen);
316
+
317
+	// not sure what this is all about...
318
+  while(r[0] === 0) {
319
+		r = r.slice(1);
320
+	}
321
+  while(s[0] === 0) {
322
+		s = s.slice(1);
323
+	}
324
+
325
+  return { r: r, s: s };
326
+}
327
+
328
+function createRsaCsr(domains, keypem, csrpub) {
297 329
   // TODO get pub from priv
298 330
   var body = createCsrBodyRsa(domains, csrpub);
299 331
   var sign = crypto.createSign('SHA256');
@@ -322,17 +354,55 @@ function createCsr(domains, keypem) {
322 354
     i += 1;
323 355
   });
324 356
 
325
-  var pem = pubkey.formatAsPublicPem(pubkey.toBase64(ab));
357
+  var pem = pubkey.formatAsPem(pubkey.toBase64(ab));
326 358
 
327 359
   return pem;
328 360
 }
329 361
 
330
-//var pem = createCsr([ 'example.com', 'www.example.com', 'api.example.com' ], keypem);
331
-var pem = createCsr([ 'whatever.net', 'api.whatever.net' ], keypem);
332
-console.log('pem:');
333
-console.log(pem);
334
-return;
362
+function createEcCsr(domains, keypem, pubj) {
363
+  // TODO get pub from priv
335 364
 
365
+  console.log('[TRACE] createEcCsr');
366
+  var body = createCsrBodyEc(domains, pubj);
367
+  console.log('csr body:', pubkey.toHex(body))
368
+  var sig = signEc(keypem, body);
369
+  //var siglen = sig.s.byteLength + sig.r.byteLength;
370
+  console.log('r:', pubkey.toHex(sig.r));
371
+  console.log('s:', pubkey.toHex(sig.s));
372
+
373
+  // XXX TODO this is junk, not real
374
+  var foot = csrEcFoot
375
+    .replace(/{s}/, pubkey.toHex(sig.s))
376
+    .replace(/{r}/, pubkey.toHex(sig.r))
377
+  ;
378
+  var len = body.byteLength + (foot.length/2) + sig.r.byteLength + sig.s.byteLength;
379
+  console.log('headlen', h(len));
380
+  var head = csrHead.replace(/{[^}]+}/, h(len));
381
+  var ab = new Uint8Array(new ArrayBuffer(4 + len));
382
+  var i = 0;
383
+  fromHex(head).forEach(function (b) {
384
+    ab[i] = b;
385
+    i += 1;
386
+  });
387
+  body.forEach(function (b) {
388
+    ab[i] = b;
389
+    i += 1;
390
+  });
391
+  fromHex(foot).forEach(function (b) {
392
+    ab[i] = b;
393
+    i += 1;
394
+  });
395
+  new Uint8Array(sig).forEach(function (b) {
396
+    ab[i] = b;
397
+    i += 1;
398
+  });
399
+
400
+  var pem = pubkey.formatAsPem(pubkey.toBase64(ab));
401
+
402
+  return pem;
403
+}
404
+
405
+/*
336 406
 function signagain(csrbody) {
337 407
   var longEnough = csrbody.byteLength > 256;
338 408
 
@@ -393,12 +463,135 @@ function signagain(csrbody) {
393 463
   //var head = csrHead.replace(/xxxx/);
394 464
   return false;
395 465
 }
466
+*/
467
+
468
+function check(domains, keypem, csrFull) {
469
+  var pemblock = pubkey.parsePem(keypem);
470
+  if ('EC' === pemblock.typ) {
471
+    console.log("EC TIME:");
472
+    console.log(pemblock);
473
+    checkEc(domains, keypem, pemblock, csrFull);
474
+  } else if ('RSA' === pemblock.typ) {
475
+    console.log("RSA TIME!");
476
+    checkRsa(domains, keypem, pemblock, csrFull);
477
+  } else {
478
+    // TODO try
479
+    throw new Error("unknown key type");
480
+  }
481
+}
482
+
483
+// TODO
484
+function checkEc(domains, keypem, pemblock, csrFull) {
485
+  var bodyend = (csrFull.byteLength - (3+2+2+32+2+32));
486
+  var sig = csrFull.slice(bodyend);
487
+
488
+  console.log();
489
+  console.log();
490
+  console.log('csr (' + csrFull.byteLength + ')');
491
+  console.log(pubkey.toHex(csrFull));
492
+  console.log();
493
+
494
+  // First 4 bytes define Segment, segment length, and content length
495
+  var csrbody = csrFull.slice(4, bodyend);
496
+  console.log();
497
+  console.log('csr body (' + csrbody.byteLength + ')');
498
+  console.log(pubkey.toHex(csrbody));
499
+  console.log();
500
+
501
+  var cnend = 21 + 2 + domains[0].length + 2 + 2 + 7 + 8;
502
+  var csrpub = csrFull.slice(cnend, cnend+2+89);
503
+  console.log('csr pub (' + csrpub.byteLength + ')');
504
+  console.log(pubkey.toHex(csrpub));
505
+  console.log();
506
+
507
+  console.log('sig (' + sig.byteLength + ')');
508
+  console.log(pubkey.toHex(sig));
509
+  console.log();
510
+
511
+  /*
512
+  var csrpem = pubToPem(csrpub);
513
+  console.log(csrpem);
514
+  console.log();
515
+
516
+  var prvpem = privateToPub(keypem);
517
+  console.log(prvpem);
518
+  console.log();
519
+
520
+  if (csrpem === prvpem) {
521
+    console.log("Public Keys Match");
522
+  } else {
523
+    throw new Error("public key read from keyfile doesn't match public key read from CSR");
524
+  }
525
+  */
526
+
527
+  var pubj = pubkey.readEcPubkey(pemblock.der);
528
+  console.log('pubj:', pubj);
529
+  var pem = createEcCsr(domains, keypem, pubj, csrpub);
530
+  console.log('EC CSR PEM:');
531
+  console.log(pem);
532
+  return;
533
+}
534
+
535
+function checkRsa(domains, keypem, pubj, csrFull) {
536
+  var sigend = (csrFull.byteLength - (2048 / 8));
537
+  var sig = csrFull.slice(sigend);
538
+
539
+  console.log();
540
+  console.log();
541
+  console.log('csr (' + csrFull.byteLength + ')');
542
+  console.log(pubkey.toHex(csrFull));
543
+  console.log();
544
+
545
+  // First 4 bytes define Segment, segment length, and content length
546
+  console.log(sigend, csrRsaFoot, csrRsaFoot.length/2);
547
+  var csrbody = csrFull.slice(4, sigend - (csrRsaFoot.length/2));
548
+  console.log(pubkey.toHex(csrbody.slice(0, csrbody.byteLength - sig.byteLength)));
549
+  console.log();
550
+  console.log('csr body (' + csrbody.byteLength + ')');
551
+  console.log(pubkey.toHex(csrbody));
552
+  console.log();
553
+
554
+  var csrpub = csrFull.slice(63 + 5, 63 + 5 + 256);
555
+  console.log('csr pub (' + csrpub.byteLength + ')');
556
+  console.log(pubkey.toHex(csrpub));
557
+  console.log();
558
+
559
+  console.log('sig (' + sig.byteLength + ')');
560
+  console.log(pubkey.toHex(sig));
561
+  console.log();
562
+
563
+  var csrpem = pubToPem(csrpub);
564
+  console.log(csrpem);
565
+  console.log();
566
+
567
+  var prvpem = privateToPub(keypem);
568
+  console.log(prvpem);
569
+  console.log();
570
+
571
+  if (csrpem === prvpem) {
572
+    console.log("Public Keys Match");
573
+  } else {
574
+    throw new Error("public key read from keyfile doesn't match public key read from CSR");
575
+  }
576
+
577
+  var pem = createRsaCsr(domains, keypem, csrpub);
578
+  console.log('RSA CSR PEM:');
579
+  console.log(pem);
580
+  return;
581
+}
582
+
583
+var keyname = process.argv[2];
584
+var dername = process.argv[3];
585
+
586
+var keypem = fs.readFileSync(keyname, 'ascii');
587
+var csrFull = fs.readFileSync(dername);
588
+var csrFull = csrFull.buffer.slice(csrFull.byteOffset, csrFull.byteOffset + csrFull.byteLength);
396 589
 
397 590
 console.log();
591
+
398 592
 console.log("CSR");
399 593
 console.log(pubkey.toHex(csrFull));
400 594
 console.log();
401
-console.log(pubkey.toHex(csrbody.slice(0, csrbody.byteLength - sig.byteLength)));
402
-console.log();
403
-console.log();
404
-//signagain(csrbody);
595
+
596
+//check([ 'whatever.net', 'api.whatever.net' ], keypem, csrFull);
597
+check([ 'example.com', 'www.example.com', 'api.example.com' ], keypem, csrFull);

Loading…
Cancel
Save