Compare commits
	
		
			2 Commits
		
	
	
		
			a2bfbf2308
			...
			e3bd35470e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e3bd35470e | |||
| 6554a8278e | 
							
								
								
									
										17
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								README.md
									
									
									
									
									
								
							| @ -43,6 +43,7 @@ eckles.import({ pem: pem }).then(function (jwk) { | |||||||
| 
 | 
 | ||||||
| * [x] SEC1/X9.62, PKCS#8, SPKI/PKIX | * [x] SEC1/X9.62, PKCS#8, SPKI/PKIX | ||||||
| * [x] P-256 (prime256v1, secp256r1), P-384 (secp384r1) | * [x] P-256 (prime256v1, secp256r1), P-384 (secp384r1) | ||||||
|  | * [x] SSH (RFC4716), (RFC 4716/SSH2) | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| var eckles = require('eckles'); | var eckles = require('eckles'); | ||||||
| @ -76,6 +77,22 @@ eckles.export({ jwk: jwk, format: 'pkcs8' }).then(function (pem) { | |||||||
| }); | }); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | `format: 'ssh'`: | ||||||
|  | 
 | ||||||
|  | Although SSH uses SEC1 for private keys, it uses ts own special non-ASN1 format | ||||||
|  | (affectionately known as rfc4716) for public keys. I got curious and then decided | ||||||
|  | to add this format as well. | ||||||
|  | 
 | ||||||
|  | To get the same format as you | ||||||
|  | would get with `ssh-keygen`, pass `ssh` as the format option: | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | eckles.export({ jwk: jwk, format: 'ssh' }).then(function (pub) { | ||||||
|  |   // Special SSH2 Public Key format (RFC 4716) | ||||||
|  |   console.log(pub); | ||||||
|  | }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| `public: 'true'`: | `public: 'true'`: | ||||||
| 
 | 
 | ||||||
| If a private key is used as input, a private key will be output. | If a private key is used as input, a private key will be output. | ||||||
|  | |||||||
| @ -16,7 +16,8 @@ try { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| if ('string' === typeof key) { | if ('string' === typeof key) { | ||||||
|   eckles.import({ pem: key }).then(function (jwk) { |   var pub = (-1 !== [ 'public', 'spki', 'pkix' ].indexOf(format)); | ||||||
|  |   eckles.import({ pem: key, public: (pub || format) }).then(function (jwk) { | ||||||
|     console.log(JSON.stringify(jwk, null, 2)); |     console.log(JSON.stringify(jwk, null, 2)); | ||||||
|   }).catch(function (err) { |   }).catch(function (err) { | ||||||
|     console.error(err); |     console.error(err); | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								fixtures/pub-ec-p256.ssh.pub
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fixtures/pub-ec-p256.ssh.pub
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCE9Uli8bGnD4hOWdeo5KKQJ/P/vOazI4MgqJK54w37emP2JwOAOdMmXuwpxbKng3KZz27mz+nKWIlXJ3rzSGMo= P-256@localhost | ||||||
							
								
								
									
										1
									
								
								fixtures/pub-ec-p384.ssh.pub
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fixtures/pub-ec-p384.ssh.pub
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBNsxFNGygmu3oyiyCfKDxpy4aoccor+P8N/CtmtEjwunbEnff4JTSfJXKr9LH3+Rm1Q+I57vN0urHJ+v03gI9xGTkRBmzrOnc6FaWroJ/l0DDMgvTuFS2wwxRgWUyZTLGw== P-384@localhost | ||||||
| @ -150,11 +150,11 @@ EC.parseSec1 = function parseEcOnlyPrivkey(u8, jwk) { | |||||||
|     kty: jwk.kty |     kty: jwk.kty | ||||||
|   , crv: jwk.crv |   , crv: jwk.crv | ||||||
|   , d: PEM._toUrlSafeBase64(d) |   , d: PEM._toUrlSafeBase64(d) | ||||||
|   //, dh: d
 |   //, dh: toHex(d)
 | ||||||
|   , x: PEM._toUrlSafeBase64(x) |   , x: PEM._toUrlSafeBase64(x) | ||||||
|   //, xh: x
 |   //, xh: toHex(x)
 | ||||||
|   , y: PEM._toUrlSafeBase64(y) |   , y: PEM._toUrlSafeBase64(y) | ||||||
|   //, yh: y
 |   //, yh: toHex(y)
 | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -187,11 +187,11 @@ EC.parsePkcs8 = function parseEcPkcs8(u8, jwk) { | |||||||
|     kty: jwk.kty |     kty: jwk.kty | ||||||
|   , crv: jwk.crv |   , crv: jwk.crv | ||||||
|   , d: PEM._toUrlSafeBase64(d) |   , d: PEM._toUrlSafeBase64(d) | ||||||
|   //, dh: d
 |   //, dh: toHex(d)
 | ||||||
|   , x: PEM._toUrlSafeBase64(x) |   , x: PEM._toUrlSafeBase64(x) | ||||||
|   //, xh: x
 |   //, xh: toHex(x)
 | ||||||
|   , y: PEM._toUrlSafeBase64(y) |   , y: PEM._toUrlSafeBase64(y) | ||||||
|   //, yh: y
 |   //, yh: toHex(y)
 | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -219,9 +219,9 @@ EC.parseSpki = function parsePem(u8, jwk) { | |||||||
|     kty: jwk.kty |     kty: jwk.kty | ||||||
|   , crv: jwk.crv |   , crv: jwk.crv | ||||||
|   , x: PEM._toUrlSafeBase64(x) |   , x: PEM._toUrlSafeBase64(x) | ||||||
|   //, xh: x
 |   //, xh: toHex(x)
 | ||||||
|   , y: PEM._toUrlSafeBase64(y) |   , y: PEM._toUrlSafeBase64(y) | ||||||
|   //, yh: y
 |   //, yh: toHex(y)
 | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| EC.parsePkix = EC.parseSpki; | EC.parsePkix = EC.parseSpki; | ||||||
| @ -244,15 +244,15 @@ EC.parse = function parseEc(opts) { | |||||||
|       // PKCS8
 |       // PKCS8
 | ||||||
|       if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) { |       if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) { | ||||||
|         //console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
 |         //console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
 | ||||||
|         return EC.parsePkcs8(u8, jwk); |         jwk = EC.parsePkcs8(u8, jwk); | ||||||
|       // EC-only
 |       // EC-only
 | ||||||
|       } else if (0x02 === u8[2] && 0x04 === u8[5] && 0xA0 === u8[39]) { |       } else if (0x02 === u8[2] && 0x04 === u8[5] && 0xA0 === u8[39]) { | ||||||
|         //console.log("EC---", u8[2].toString(16), u8[5].toString(16), u8[39].toString(16));
 |         //console.log("EC---", u8[2].toString(16), u8[5].toString(16), u8[39].toString(16));
 | ||||||
|         return EC.parseSec1(u8, jwk); |         jwk = EC.parseSec1(u8, jwk); | ||||||
|       // SPKI/PKIK (Public)
 |       // SPKI/PKIK (Public)
 | ||||||
|       } else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) { |       } else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) { | ||||||
|         //console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
 |         //console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
 | ||||||
|         return EC.parseSpki(u8, jwk); |         jwk = EC.parseSpki(u8, jwk); | ||||||
|       // Error
 |       // Error
 | ||||||
|       } else { |       } else { | ||||||
|         //console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
 |         //console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
 | ||||||
| @ -266,15 +266,15 @@ EC.parse = function parseEc(opts) { | |||||||
|       // PKCS8
 |       // PKCS8
 | ||||||
|       if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) { |       if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) { | ||||||
|         //console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
 |         //console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
 | ||||||
|         return EC.parsePkcs8(u8, jwk); |         jwk = EC.parsePkcs8(u8, jwk); | ||||||
|       // EC-only
 |       // EC-only
 | ||||||
|       } else if (0x02 === u8[3] && 0x04 === u8[6] && 0xA0 === u8[56]) { |       } else if (0x02 === u8[3] && 0x04 === u8[6] && 0xA0 === u8[56]) { | ||||||
|         //console.log("EC---", u8[3].toString(16), u8[6].toString(16), u8[56].toString(16));
 |         //console.log("EC---", u8[3].toString(16), u8[6].toString(16), u8[56].toString(16));
 | ||||||
|         return EC.parseSec1(u8, jwk); |         jwk = EC.parseSec1(u8, jwk); | ||||||
|       // SPKI/PKIK (Public)
 |       // SPKI/PKIK (Public)
 | ||||||
|       } else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) { |       } else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) { | ||||||
|         //console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
 |         //console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
 | ||||||
|         return EC.parseSpki(u8, jwk); |         jwk = EC.parseSpki(u8, jwk); | ||||||
|       // Error
 |       // Error
 | ||||||
|       } else { |       } else { | ||||||
|         //console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
 |         //console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
 | ||||||
| @ -285,6 +285,14 @@ EC.parse = function parseEc(opts) { | |||||||
|     } else { |     } else { | ||||||
|       throw new Error("Supported key types are P-256 and P-384"); |       throw new Error("Supported key types are P-256 and P-384"); | ||||||
|     } |     } | ||||||
|  |     if (opts.public) { | ||||||
|  |       if (true !== opts.public) { | ||||||
|  |         throw new Error("options.public must be either `true` or `false` not (" | ||||||
|  |           + typeof opts.public + ") '" + opts.public + "'"); | ||||||
|  |       } | ||||||
|  |       delete jwk.d; | ||||||
|  |     } | ||||||
|  |     return jwk; | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| EC.toJwk = EC.import = EC.parse; | EC.toJwk = EC.import = EC.parse; | ||||||
| @ -296,17 +304,20 @@ EC.pack = function (opts) { | |||||||
|     } |     } | ||||||
|     var jwk = JSON.parse(JSON.stringify(opts.jwk)); |     var jwk = JSON.parse(JSON.stringify(opts.jwk)); | ||||||
|     var format = opts.format; |     var format = opts.format; | ||||||
|     if (opts.public || -1 !== [ 'spki', 'pkix' ].indexOf(format)) { |     if (opts.public || -1 !== [ 'spki', 'pkix', 'ssh', 'rfc4716' ].indexOf(format)) { | ||||||
|       jwk.d = null; |       jwk.d = null; | ||||||
|     } |     } | ||||||
|     if ('EC' !== jwk.kty) { |     if ('EC' !== jwk.kty) { | ||||||
|       throw new Error("options.jwk.kty must be 'EC' for EC keys"); |       throw new Error("options.jwk.kty must be 'EC' for EC keys"); | ||||||
|     } |     } | ||||||
|     if (!jwk.d) { |     if (!jwk.d) { | ||||||
|       if (!format || 'pkix' === format) { |       if (!format || -1 !== [ 'spki', 'pkix' ].indexOf(format)) { | ||||||
|         format = 'spki'; |         format = 'spki'; | ||||||
|       } else if ('spki' !== format) { |       } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) { | ||||||
|         throw new Error("options.format must be 'spki' for public EC keys"); |         format = 'ssh'; | ||||||
|  |       } else { | ||||||
|  |         throw new Error("options.format must be 'spki' or 'ssh' for public EC keys, not (" | ||||||
|  |           + typeof format + ") " + format); | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       if (!format || 'sec1' === format) { |       if (!format || 'sec1' === format) { | ||||||
| @ -326,8 +337,12 @@ EC.pack = function (opts) { | |||||||
|       return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: EC.packSec1(jwk) }); |       return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: EC.packSec1(jwk) }); | ||||||
|     } else if ('pkcs8' === format) { |     } else if ('pkcs8' === format) { | ||||||
|       return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: EC.packPkcs8(jwk) }); |       return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: EC.packPkcs8(jwk) }); | ||||||
|     } else { |     } else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) { | ||||||
|       return PEM.packBlock({ type: "PUBLIC KEY", bytes: EC.packSpki(jwk) }); |       return PEM.packBlock({ type: "PUBLIC KEY", bytes: EC.packSpki(jwk) }); | ||||||
|  |     } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) { | ||||||
|  |       return EC.packSsh(jwk); | ||||||
|  |     } else { | ||||||
|  |       throw new Error("Sanity Error: reached unreachable code block with format: " + format); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| @ -378,6 +393,27 @@ EC.packSpki = function (jwk) { | |||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
| EC.packPkix = EC.packSpki; | EC.packPkix = EC.packSpki; | ||||||
|  | EC.packSsh = function (jwk) { | ||||||
|  |   // Custom SSH format
 | ||||||
|  |   var typ = 'ecdsa-sha2-nistp256'; | ||||||
|  | 	var a = '32 35 36'; | ||||||
|  |   var b = '41'; | ||||||
|  |   var comment = jwk.crv + '@localhost'; | ||||||
|  |   if ('P-256' !== jwk.crv) { | ||||||
|  |     typ = 'ecdsa-sha2-nistp384'; | ||||||
|  |     a = '33 38 34'; | ||||||
|  |     b = '61'; | ||||||
|  |   } | ||||||
|  |   var x = toHex(base64ToUint8(urlBase64ToBase64(jwk.x))); | ||||||
|  |   var y = toHex(base64ToUint8(urlBase64ToBase64(jwk.y))); | ||||||
|  |   var ssh = Hex.toUint8( | ||||||
|  |     ('00 00 00 13 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70' | ||||||
|  |     + a + '00 00 00 08 6e 69 73 74 70' + a + '00 00 00' + b | ||||||
|  |     + '04' + x + y).replace(/\s+/g, '').toLowerCase() | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   return typ + ' ' + toBase64(ssh) + ' ' + comment; | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| //
 | //
 | ||||||
| // A dumbed-down, minimal ASN.1 packer
 | // A dumbed-down, minimal ASN.1 packer
 | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "eckles", |   "name": "eckles", | ||||||
|   "version": "1.0.1", |   "version": "1.1.0", | ||||||
|   "description": "PEM-to-JWK and JWK-to-PEM for ECDSA keys in a lightweight, zero-dependency library focused on perfect universal compatibility.", |   "description": "PEM-to-JWK and JWK-to-PEM for ECDSA keys in a lightweight, zero-dependency library focused on perfect universal compatibility.", | ||||||
|   "homepage": "https://git.coolaj86.com/coolaj86/eckles.js", |   "homepage": "https://git.coolaj86.com/coolaj86/eckles.js", | ||||||
|   "main": "index.js", |   "main": "index.js", | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								test.sh
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								test.sh
									
									
									
									
									
								
							| @ -29,10 +29,15 @@ echo "Testing JWK-to-PEM P-256" | |||||||
| echo "" | echo "" | ||||||
| node bin/eckles.js fixtures/privkey-ec-p256.jwk.json sec1 | tee fixtures/privkey-ec-p256.sec1.pem.2 | node bin/eckles.js fixtures/privkey-ec-p256.jwk.json sec1 | tee fixtures/privkey-ec-p256.sec1.pem.2 | ||||||
| diff fixtures/privkey-ec-p256.sec1.pem fixtures/privkey-ec-p256.sec1.pem.2 | diff fixtures/privkey-ec-p256.sec1.pem fixtures/privkey-ec-p256.sec1.pem.2 | ||||||
|  | # | ||||||
| node bin/eckles.js fixtures/privkey-ec-p256.jwk.json pkcs8 | tee fixtures/privkey-ec-p256.pkcs8.pem.2 | node bin/eckles.js fixtures/privkey-ec-p256.jwk.json pkcs8 | tee fixtures/privkey-ec-p256.pkcs8.pem.2 | ||||||
| diff fixtures/privkey-ec-p256.pkcs8.pem fixtures/privkey-ec-p256.pkcs8.pem.2 | diff fixtures/privkey-ec-p256.pkcs8.pem fixtures/privkey-ec-p256.pkcs8.pem.2 | ||||||
|  | # | ||||||
| node bin/eckles.js fixtures/pub-ec-p256.jwk.json spki | tee fixtures/pub-ec-p256.spki.pem.2 | node bin/eckles.js fixtures/pub-ec-p256.jwk.json spki | tee fixtures/pub-ec-p256.spki.pem.2 | ||||||
| diff fixtures/pub-ec-p256.spki.pem fixtures/pub-ec-p256.spki.pem.2 | diff fixtures/pub-ec-p256.spki.pem fixtures/pub-ec-p256.spki.pem.2 | ||||||
|  | # ssh-keygen -f fixtures/pub-ec-p256.spki.pem -i -mPKCS8 > fixtures/pub-ec-p256.ssh.pub | ||||||
|  | node bin/eckles.js fixtures/pub-ec-p256.jwk.json ssh | tee fixtures/pub-ec-p256.ssh.pub.2 | ||||||
|  | diff fixtures/pub-ec-p256.ssh.pub fixtures/pub-ec-p256.ssh.pub.2 | ||||||
| 
 | 
 | ||||||
| echo "" | echo "" | ||||||
| echo "" | echo "" | ||||||
| @ -40,10 +45,15 @@ echo "Testing JWK-to-PEM P-384" | |||||||
| echo "" | echo "" | ||||||
| node bin/eckles.js fixtures/privkey-ec-p384.jwk.json sec1 | tee fixtures/privkey-ec-p384.sec1.pem.2 | node bin/eckles.js fixtures/privkey-ec-p384.jwk.json sec1 | tee fixtures/privkey-ec-p384.sec1.pem.2 | ||||||
| diff fixtures/privkey-ec-p384.sec1.pem fixtures/privkey-ec-p384.sec1.pem.2 | diff fixtures/privkey-ec-p384.sec1.pem fixtures/privkey-ec-p384.sec1.pem.2 | ||||||
|  | # | ||||||
| node bin/eckles.js fixtures/privkey-ec-p384.jwk.json pkcs8 | tee fixtures/privkey-ec-p384.pkcs8.pem.2 | node bin/eckles.js fixtures/privkey-ec-p384.jwk.json pkcs8 | tee fixtures/privkey-ec-p384.pkcs8.pem.2 | ||||||
| diff fixtures/privkey-ec-p384.pkcs8.pem fixtures/privkey-ec-p384.pkcs8.pem.2 | diff fixtures/privkey-ec-p384.pkcs8.pem fixtures/privkey-ec-p384.pkcs8.pem.2 | ||||||
|  | # | ||||||
| node bin/eckles.js fixtures/pub-ec-p384.jwk.json spki | tee fixtures/pub-ec-p384.spki.pem.2 | node bin/eckles.js fixtures/pub-ec-p384.jwk.json spki | tee fixtures/pub-ec-p384.spki.pem.2 | ||||||
| diff fixtures/pub-ec-p384.spki.pem fixtures/pub-ec-p384.spki.pem.2 | diff fixtures/pub-ec-p384.spki.pem fixtures/pub-ec-p384.spki.pem.2 | ||||||
|  | # ssh-keygen -f fixtures/pub-ec-p384.spki.pem -i -mPKCS8 > fixtures/pub-ec-p384.ssh.pub | ||||||
|  | node bin/eckles.js fixtures/pub-ec-p384.jwk.json ssh | tee fixtures/pub-ec-p384.ssh.pub.2 | ||||||
|  | diff fixtures/pub-ec-p384.ssh.pub fixtures/pub-ec-p384.ssh.pub.2 | ||||||
| 
 | 
 | ||||||
| rm fixtures/*.2 | rm fixtures/*.2 | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user