v0.8.0: JWK-to-PEM for PKCS#1 and SSH
This commit is contained in:
		
							parent
							
								
									895a29bf71
								
							
						
					
					
						commit
						607e352b17
					
				| @ -17,7 +17,8 @@ It is considered to be complete, but if you find a bug please open an issue. --> | |||||||
| 
 | 
 | ||||||
| ## PEM-to-JWK | ## PEM-to-JWK | ||||||
| 
 | 
 | ||||||
| * [x] PKCS#1 (traditional), PKCS#8, SPKI/PKIX | * [x] PKCS#1 (traditional) | ||||||
|  | * [x] PKCS#8, SPKI/PKIX | ||||||
| * [x] 2048-bit, 4096-bit (and ostensibily all others) | * [x] 2048-bit, 4096-bit (and ostensibily all others) | ||||||
| * [x] SSH (RFC4716), (RFC 4716/SSH2) | * [x] SSH (RFC4716), (RFC 4716/SSH2) | ||||||
| 
 | 
 | ||||||
| @ -45,16 +46,16 @@ Rasha.import({ pem: pem }).then(function (jwk) { | |||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| <!-- |  | ||||||
| ## JWK-to-PEM | ## JWK-to-PEM | ||||||
| 
 | 
 | ||||||
| * [x] PKCS#1 (traditional), PKCS#8, SPKI/PKIX | * [x] PKCS#1 (traditional) | ||||||
|  | * [ ] PKCS#8, SPKI/PKIX | ||||||
| * [x] 2048-bit, 4096-bit (and ostensibily all others) | * [x] 2048-bit, 4096-bit (and ostensibily all others) | ||||||
| * [x] SSH (RFC4716), (RFC 4716/SSH2) | * [x] SSH (RFC4716), (RFC 4716/SSH2) | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| var Rasha = require('rasha'); | var Rasha = require('rasha'); | ||||||
| var jwk = require('rasha/fixtures/privkey-rsa-2038.jwk.json'); | var jwk = require('rasha/fixtures/privkey-rsa-2048.jwk.json'); | ||||||
| 
 | 
 | ||||||
| Rasha.export({ jwk: jwk }).then(function (pem) { | Rasha.export({ jwk: jwk }).then(function (pem) { | ||||||
|   // PEM in PKCS1 (traditional) format |   // PEM in PKCS1 (traditional) format | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								fixtures/pub-rsa-2048.jwk.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								fixtures/pub-rsa-2048.jwk.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | { | ||||||
|  |   "kty": "RSA", | ||||||
|  |   "n": "m2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJefLukC-xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57qAZM6-I70on0_iDZm7-jcqOPgADAmbWHhy67BXkk4yy_YzD4yOGZFXZcNp915_TW5bRd__AKPHUHxJasPiyEFqlNKBR2DSD-LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0Qi49OykUCfNZeQlEz7UNNj9RGps_50-CNw", | ||||||
|  |   "e": "AQAB" | ||||||
|  | } | ||||||
| @ -15,6 +15,10 @@ Enc.bufToHex = function toHex(u8) { | |||||||
|   return hex.join('').toLowerCase(); |   return hex.join('').toLowerCase(); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | Enc.hexToBase64 = function (hex) { | ||||||
|  |   return Buffer.from(hex, 'hex').toString('base64'); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| Enc.hexToBuf = function (hex) { | Enc.hexToBuf = function (hex) { | ||||||
|   return Buffer.from(hex, 'hex'); |   return Buffer.from(hex, 'hex'); | ||||||
| }; | }; | ||||||
| @ -29,7 +33,7 @@ Enc.numToHex = function numToHex(d) { | |||||||
| 
 | 
 | ||||||
| Enc.base64ToHex = function base64ToHex(b64) { | Enc.base64ToHex = function base64ToHex(b64) { | ||||||
|   return Enc.bufToHex(Enc.base64ToBuf(b64)); |   return Enc.bufToHex(Enc.base64ToBuf(b64)); | ||||||
| } | }; | ||||||
| 
 | 
 | ||||||
| Enc.bufToBase64 = function toHex(u8) { | Enc.bufToBase64 = function toHex(u8) { | ||||||
|   // we want to maintain api compatability with browser APIs,
 |   // we want to maintain api compatability with browser APIs,
 | ||||||
| @ -37,11 +41,25 @@ Enc.bufToBase64 = function toHex(u8) { | |||||||
|   return Buffer.from(u8).toString('base64'); |   return Buffer.from(u8).toString('base64'); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /* | ||||||
|  | Enc.bufToUint8 = function bufToUint8(buf) { | ||||||
|  |   return new Uint8Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)); | ||||||
|  | }; | ||||||
|  | */ | ||||||
|  | 
 | ||||||
| Enc.bufToUrlBase64 = function toHex(u8) { | Enc.bufToUrlBase64 = function toHex(u8) { | ||||||
|   return Enc.bufToBase64(u8) |   return Enc.bufToBase64(u8) | ||||||
|     .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); |     .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | Enc.strToHex = function strToHex(str) { | ||||||
|  |   return Buffer.from(str).toString('hex'); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Enc.strToBuf = function strToBuf(str) { | ||||||
|  |   return Buffer.from(str); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /* | /* | ||||||
| Enc.strToBin = function strToBin(str) { | Enc.strToBin = function strToBin(str) { | ||||||
|   var escstr = encodeURIComponent(str); |   var escstr = encodeURIComponent(str); | ||||||
|  | |||||||
| @ -100,7 +100,7 @@ RSA.pack = function (opts) { | |||||||
|     } else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) { |     } else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) { | ||||||
|       return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) }); |       return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) }); | ||||||
|     } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) { |     } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) { | ||||||
|       return SSH.packSsh(jwk); |       return SSH.pack({ jwk: jwk, comment: opts.comment }); | ||||||
|     } else { |     } else { | ||||||
|       throw new Error("Sanity Error: reached unreachable code block with format: " + format); |       throw new Error("Sanity Error: reached unreachable code block with format: " + format); | ||||||
|     } |     } | ||||||
|  | |||||||
							
								
								
									
										44
									
								
								lib/ssh.js
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								lib/ssh.js
									
									
									
									
									
								
							| @ -17,6 +17,7 @@ SSH.parse = function (pem, jwk) { | |||||||
|   var offset = (buf.byteOffset || 0); |   var offset = (buf.byteOffset || 0); | ||||||
|   // using dataview to be browser-compatible (I do want _some_ code reuse)
 |   // using dataview to be browser-compatible (I do want _some_ code reuse)
 | ||||||
|   var dv = new DataView(buf.buffer.slice(offset, offset + buf.byteLength)); |   var dv = new DataView(buf.buffer.slice(offset, offset + buf.byteLength)); | ||||||
|  |   var el; | ||||||
| 
 | 
 | ||||||
|   if (SSH.RSA !== Enc.bufToHex(buf.slice(0, SSH.RSA.length/2))) { |   if (SSH.RSA !== Enc.bufToHex(buf.slice(0, SSH.RSA.length/2))) { | ||||||
|     throw new Error("does not lead with ssh header"); |     throw new Error("does not lead with ssh header"); | ||||||
| @ -27,7 +28,12 @@ SSH.parse = function (pem, jwk) { | |||||||
|     if (i > 3) { throw new Error("15+ elements, probably not a public ssh key"); } |     if (i > 3) { throw new Error("15+ elements, probably not a public ssh key"); } | ||||||
|     len = dv.getUint32(index, false); |     len = dv.getUint32(index, false); | ||||||
|     index += 4; |     index += 4; | ||||||
|     els.push(buf.slice(index, index + len)); |     el = buf.slice(index, index + len); | ||||||
|  |     // remove BigUInt '00' prefix
 | ||||||
|  |     if (0x00 === el[0]) { | ||||||
|  |       el = el.slice(1); | ||||||
|  |     } | ||||||
|  |     els.push(el); | ||||||
|     index += len; |     index += len; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -36,3 +42,39 @@ SSH.parse = function (pem, jwk) { | |||||||
| 
 | 
 | ||||||
|   return jwk; |   return jwk; | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | SSH.pack = function (opts) { | ||||||
|  |   var jwk = opts.jwk; | ||||||
|  |   var header = 'ssh-rsa'; | ||||||
|  |   var comment = opts.comment || 'rsa@localhost'; | ||||||
|  |   var e = SSH._padHexInt(Enc.base64ToHex(jwk.e)); | ||||||
|  |   var n = SSH._padHexInt(Enc.base64ToHex(jwk.n)); | ||||||
|  |   var hex = [ | ||||||
|  |     SSH._numToUint32Hex(header.length) | ||||||
|  |   , Enc.strToHex(header) | ||||||
|  |   , SSH._numToUint32Hex(e.length/2) | ||||||
|  |   , e | ||||||
|  |   , SSH._numToUint32Hex(n.length/2) | ||||||
|  |   , n | ||||||
|  |   ].join(''); | ||||||
|  |   return [ header, Enc.hexToBase64(hex), comment ].join(' '); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | SSH._numToUint32Hex = function (num) { | ||||||
|  |   var hex = num.toString(16); | ||||||
|  |   while (hex.length < 8) { | ||||||
|  |     hex = '0' + hex; | ||||||
|  |   } | ||||||
|  |   return hex; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | SSH._padHexInt = function (hex) { | ||||||
|  |   // BigInt is negative if the high order bit 0x80 is set,
 | ||||||
|  |   // so ASN1, SSH, and many other formats pad with '0x00'
 | ||||||
|  |   // to signifiy a positive number.
 | ||||||
|  |   var i = parseInt(hex.slice(0, 2), 16); | ||||||
|  |   if (0x80 & i) { | ||||||
|  |     return '00' + hex; | ||||||
|  |   } | ||||||
|  |   return hex; | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "rasha", |   "name": "rasha", | ||||||
|   "version": "0.7.1", |   "version": "0.8.0", | ||||||
|   "description": "PEM-to-JWK and JWK-to-PEM for RSA keys in a lightweight, zero-dependency library focused on perfect universal compatibility.", |   "description": "PEM-to-JWK and JWK-to-PEM for RSA keys in a lightweight, zero-dependency library focused on perfect universal compatibility.", | ||||||
|   "homepage": "https://git.coolaj86.com/coolaj86/rasha.js", |   "homepage": "https://git.coolaj86.com/coolaj86/rasha.js", | ||||||
|   "main": "index.js", |   "main": "index.js", | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user