|
1 | 1 | <template>
|
2 |
| - <div class="hello"> |
3 |
| - ssh component works! |
| 2 | +<div class="container"> |
| 3 | + <div class="input-group mb-3 col-md-6 offset-md-3"> |
| 4 | + <input id="name-ssh-generate" type="text" class="form-control" v-model="username" placeholder="SSH Key name" aria-label="Recipient's username" aria-describedby="button-addon2"> |
| 5 | + <div class="input-group-append"> |
| 6 | + <button :disabled="!username" @click='getKeys' class="btn btn-primary" type="button" id="button-addon2">Generate</button> |
| 7 | + </div> |
| 8 | +</div> |
| 9 | + <div v-if="sended" class="col-md-12"> |
| 10 | + <div class="mb-5"> |
| 11 | + <p>id_rsa</p> |
| 12 | + <textarea readonly id="generate-ssh-id-rsa" v-model="idRsa" /> |
| 13 | + <button |
| 14 | + class="btn-icon btn-icon-transparent btn-small fas fa-paste" |
| 15 | + data-toggle="tooltip" |
| 16 | + @click="copyKey('generate-ssh-id-rsa')" |
| 17 | + data-placement="bottom" |
| 18 | + title="Copy id_rsa" |
| 19 | + /> |
| 20 | + <button |
| 21 | + class="btn-icon btn-icon-transparent btn-small fas fa-download" |
| 22 | + data-toggle="tooltip" |
| 23 | + @click="downloadKeyFile('id_rsa', idRsa)" |
| 24 | + data-placement="bottom" |
| 25 | + title="Download Key File" |
| 26 | + /> |
| 27 | + </div> |
| 28 | + <hr /> |
| 29 | + |
| 30 | + <div class="id-rsa-pub-div"> |
| 31 | + <p>id_rsa.pub</p> |
| 32 | + <textarea readonly id="generate-ssh-id-rsa-pub" v-model="idRsaPub" /> |
| 33 | + <button |
| 34 | + class="btn-icon fas fa-paste" |
| 35 | + data-toggle="tooltip" |
| 36 | + @click="copyKey('generate-ssh-id-rsa-pub')" |
| 37 | + data-placement="bottom" |
| 38 | + title="Copy id_rsa.pub" |
| 39 | + /> |
| 40 | + <button |
| 41 | + class="btn-icon fas fa-download" |
| 42 | + data-toggle="tooltip" |
| 43 | + @click="downloadKeyFile('id_rsa.pub', idRsaPub)" |
| 44 | + data-placement="bottom" |
| 45 | + title="Download Key File" |
| 46 | + /> |
| 47 | + </div> |
| 48 | + </div> |
4 | 49 | </div>
|
5 | 50 | </template>
|
6 | 51 |
|
7 | 52 | <script>
|
8 | 53 | export default {
|
9 |
| - name: 'HelloWorld', |
| 54 | + name: 'sshComponent', |
10 | 55 | props: {
|
11 | 56 | msg: String
|
| 57 | + }, |
| 58 | + data() { |
| 59 | + return { |
| 60 | + sended: false, |
| 61 | + loading: false, |
| 62 | + email: undefined, |
| 63 | + idRsa: undefined, |
| 64 | + idRsaPub: undefined, |
| 65 | + username: undefined, |
| 66 | + isClicked: undefined, |
| 67 | + isDownloaded: false, |
| 68 | + isChecked: false, |
| 69 | + btnEnabled: undefined |
| 70 | + }; |
| 71 | + }, |
| 72 | + methods: { |
| 73 | + btnEnable() { |
| 74 | + document.getElementById('checkbox-if-downloaded').checked |
| 75 | + ? (this.btnEnabled = true) |
| 76 | + : (this.btnEnabled = false); |
| 77 | + }, |
| 78 | + copyKey(element) { |
| 79 | + document.getElementById(element).select(); |
| 80 | + document.execCommand('copy'); |
| 81 | + this.$snotify.success('The key was copied to you clipboard', ''); |
| 82 | + }, |
| 83 | + downloadKeyFile(name, content) { |
| 84 | + let atag = document.createElement('a'); |
| 85 | + let file = new Blob([content]); |
| 86 | + atag.href = URL.createObjectURL(file); |
| 87 | + atag.download = name; |
| 88 | + atag.click(); |
| 89 | + this.$snotify.success('Your download is starting...', ''); |
| 90 | + }, |
| 91 | + getKeys() { |
| 92 | + this.sended = true; |
| 93 | + const extractable = true; |
| 94 | + var name = 'nome da chave', |
| 95 | + alg = 'RSASSA-PKCS1-v1_5', |
| 96 | + size = 1024; |
| 97 | + generateKeyPair(alg, size, name) |
| 98 | + .then(keys => { |
| 99 | + // 'get id rsa' and 'id rsa pub'... |
| 100 | + this.idRsa = `-----BEGIN RSA PRIVATE KEY-----\n${ |
| 101 | + keys[0] |
| 102 | + }-----END RSA PRIVATE KEY-----`; |
| 103 | + this.idRsaPub = keys[1].replace( |
| 104 | + 'RSASSA-PKCS1-v1_5', |
| 105 | + `${this.username || 'name'}` |
| 106 | + ); |
| 107 | + }) |
| 108 | + .catch(() => { |
| 109 | + this.$snotify.error('', 'Error! Please, try again...'); |
| 110 | + }) |
| 111 | + .finally(() => { |
| 112 | + this.loading = false; |
| 113 | + }); |
| 114 | +
|
| 115 | + function wrap(text, len) { |
| 116 | + const length = len || 72; |
| 117 | + let result = ''; |
| 118 | + for (let i = 0; i < text.length; i += length) { |
| 119 | + result += text.slice(i, i + length); |
| 120 | + result += '\n'; |
| 121 | + } |
| 122 | + return result; |
| 123 | + } |
| 124 | +
|
| 125 | + function rsaPrivateKey(key) { |
| 126 | + return `-----BEGIN RSA PRIVATE KEY-----\n${key}-----END RSA PRIVATE KEY-----`; |
| 127 | + } |
| 128 | +
|
| 129 | + function arrayBufferToBase64(buffer) { |
| 130 | + let binary = ''; |
| 131 | + const bytes = new Uint8Array(buffer); |
| 132 | + const len = bytes.byteLength; |
| 133 | + for (let i = 0; i < len; i += 1) { |
| 134 | + binary += String.fromCharCode(bytes[i]); |
| 135 | + } |
| 136 | + return window.btoa(binary); |
| 137 | + } |
| 138 | +
|
| 139 | + function generateKeyPair(name) { |
| 140 | + return window.crypto.subtle |
| 141 | + .generateKey( |
| 142 | + { |
| 143 | + name: 'RSASSA-PKCS1-v1_5', |
| 144 | + modulusLength: 2048, // 1024, 2048, 4096 |
| 145 | + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), |
| 146 | + hash: { name: 'SHA-1' } // SHA-1, SHA-256, SHA-384, SHA-512 |
| 147 | + }, |
| 148 | + true, |
| 149 | + ['sign', 'verify'] |
| 150 | + ) |
| 151 | + .then(key => { |
| 152 | + const privateKey = window.crypto.subtle |
| 153 | + .exportKey('jwk', key.privateKey) |
| 154 | + .then(encodePrivateKey) |
| 155 | + .then(wrap) |
| 156 | + .then(rsaPrivateKey()); |
| 157 | +
|
| 158 | + const publicKey = window.crypto.subtle |
| 159 | + .exportKey('jwk', key.publicKey) |
| 160 | + .then(jwk => encodePublicKey(jwk, name)); |
| 161 | + return Promise.all([privateKey, publicKey]); |
| 162 | + }); |
| 163 | + } |
| 164 | +
|
| 165 | + function arrayToString(a) { |
| 166 | + return String.fromCharCode.apply(null, a); |
| 167 | + } |
| 168 | +
|
| 169 | + function stringToArray(s) { |
| 170 | + return s.split('').map(c => c.charCodeAt()); |
| 171 | + } |
| 172 | +
|
| 173 | + function pemToArray(pem) { |
| 174 | + return stringToArray(window.atob(pem)); |
| 175 | + } |
| 176 | +
|
| 177 | + function arrayToPem(a) { |
| 178 | + return window.btoa(a.map(c => String.fromCharCode(c)).join('')); |
| 179 | + } |
| 180 | +
|
| 181 | + function arrayToLen(a) { |
| 182 | + let result = 0; |
| 183 | + for (let i = 0; i < a.length; i += 1) { |
| 184 | + result = result * 256 + a[i]; |
| 185 | + } |
| 186 | + return result; |
| 187 | + } |
| 188 | +
|
| 189 | + function integerToOctet(n) { |
| 190 | + const result = []; |
| 191 | + for (let i = n; i > 0; i >>= 8) { |
| 192 | + result.push(i & 0xff); |
| 193 | + } |
| 194 | + return result.reverse(); |
| 195 | + } |
| 196 | +
|
| 197 | + function lenToArray(n) { |
| 198 | + const oct = integerToOctet(n); |
| 199 | + let i; |
| 200 | + for (i = oct.length; i < 4; i += 1) { |
| 201 | + oct.unshift(0); |
| 202 | + } |
| 203 | + return oct; |
| 204 | + } |
| 205 | +
|
| 206 | + function decodePublicKey(s) { |
| 207 | + const split = s.split(' '); |
| 208 | + const prefix = split[0]; |
| 209 | + if (prefix !== 'ssh-rsa') { |
| 210 | + throw new Error(`Unknown prefix: ${prefix}`); |
| 211 | + } |
| 212 | + const buffer = pemToArray(split[1]); |
| 213 | + const nameLen = arrayToLen(buffer.splice(0, 4)); |
| 214 | + const type = arrayToString(buffer.splice(0, nameLen)); |
| 215 | + if (type !== 'ssh-rsa') { |
| 216 | + throw new Error(`Unknown key type: ${type}`); |
| 217 | + } |
| 218 | + const exponentLen = arrayToLen(buffer.splice(0, 4)); |
| 219 | + const exponent = buffer.splice(0, exponentLen); |
| 220 | + const keyLen = arrayToLen(buffer.splice(0, 4)); |
| 221 | + const key = buffer.splice(0, keyLen); |
| 222 | + return { type, exponent, key, name: split[2] }; |
| 223 | + } |
| 224 | +
|
| 225 | + function checkHighestBit(v) { |
| 226 | + if (v[0] >> 7 === 1) { |
| 227 | + // addd leading zero if first bit is set |
| 228 | + v.unshift(0); |
| 229 | + } |
| 230 | + return v; |
| 231 | + } |
| 232 | +
|
| 233 | + function jwkToInternal(jwk) { |
| 234 | + return { |
| 235 | + type: 'ssh-rsa', |
| 236 | + exponent: checkHighestBit(stringToArray(base64urlDecode(jwk.e))), |
| 237 | + name: 'name', |
| 238 | + key: checkHighestBit(stringToArray(base64urlDecode(jwk.n))) |
| 239 | + }; |
| 240 | + } |
| 241 | +
|
| 242 | + function encodePublicKey(jwk, name) { |
| 243 | + const k = jwkToInternal(jwk); |
| 244 | + k.name = name; |
| 245 | + const keyLenA = lenToArray(k.key.length); |
| 246 | + const exponentLenA = lenToArray(k.exponent.length); |
| 247 | + const typeLenA = lenToArray(k.type.length); |
| 248 | + const array = [].concat( |
| 249 | + typeLenA, |
| 250 | + stringToArray(k.type), |
| 251 | + exponentLenA, |
| 252 | + k.exponent, |
| 253 | + keyLenA, |
| 254 | + k.key |
| 255 | + ); |
| 256 | + const encoding = arrayToPem(array); |
| 257 | + return `${k.type} ${encoding} ${k.name}`; |
| 258 | + } |
| 259 | +
|
| 260 | + function asnEncodeLen(n) { |
| 261 | + let result = []; |
| 262 | + if (n >> 7) { |
| 263 | + result = integerToOctet(n); |
| 264 | + result.unshift(0x80 + result.length); |
| 265 | + } else { |
| 266 | + result.push(n); |
| 267 | + } |
| 268 | + return result; |
| 269 | + } |
| 270 | +
|
| 271 | + function encodePrivateKey(jwk) { |
| 272 | + const order = ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']; |
| 273 | + const list = order.map(prop => { |
| 274 | + const v = checkHighestBit(stringToArray(base64urlDecode(jwk[prop]))); |
| 275 | + const len = asnEncodeLen(v.length); |
| 276 | + return [0x02].concat(len, v); // int. tag is 0x02 |
| 277 | + }); |
| 278 | + let seq = [0x02, 0x01, 0x00]; // extra seq. for SSH |
| 279 | + seq = seq.concat(...list); |
| 280 | + const len = asnEncodeLen(seq.length); |
| 281 | + const a = [0x30].concat(len, seq); // seq. is 0x30 |
| 282 | + return arrayToPem(a); |
| 283 | + } |
| 284 | +
|
| 285 | + function base64urlEncode(arg) { |
| 286 | + const step1 = window.btoa(arg); // Regular base64 encoder |
| 287 | + const step2 = step1.split('=')[0]; // Remove any trailing '='s |
| 288 | + const step3 = step2.replace(/\+/g, '-'); // 62nd char of encoding |
| 289 | + const step4 = step3.replace(/\//g, '_'); // 63rd char of encoding |
| 290 | + return step4; |
| 291 | + } |
| 292 | +
|
| 293 | + function base64urlDecode(s) { |
| 294 | + const step1 = s.replace(/-/g, '+'); // 62nd char of encoding |
| 295 | + const step2 = step1.replace(/_/g, '/'); // 63rd char of encoding |
| 296 | + let step3 = step2; |
| 297 | + switch (step2.length % 4) { // Pad with trailing '='s |
| 298 | + case 0: // No pad chars in this case |
| 299 | + break; |
| 300 | + case 2: // Two pad chars |
| 301 | + step3 += '=='; |
| 302 | + break; |
| 303 | + case 3: // One pad char |
| 304 | + step3 += '='; |
| 305 | + break; |
| 306 | + default: |
| 307 | + throw new Error('Illegal base64url string!'); |
| 308 | + } |
| 309 | + return window.atob(step3); // Regular base64 decoder |
| 310 | + } |
| 311 | + } |
12 | 312 | }
|
13 |
| -} |
| 313 | +}; |
14 | 314 | </script>
|
15 | 315 |
|
16 |
| -<!-- Add "scoped" attribute to limit CSS to this component only --> |
17 | 316 | <style scoped lang="scss">
|
18 |
| -h3 { |
19 |
| - margin: 40px 0 0; |
20 |
| -} |
21 |
| -ul { |
22 |
| - list-style-type: none; |
23 |
| - padding: 0; |
| 317 | +#generate-ssh-id-rsa, |
| 318 | +#generate-ssh-id-rsa-pub { |
| 319 | + width: 100%; |
| 320 | + height: 200px; |
| 321 | + text-align: center; |
24 | 322 | }
|
25 |
| -li { |
26 |
| - display: inline-block; |
27 |
| - margin: 0 10px; |
| 323 | +.btn-icon { |
| 324 | + background-color: transparent; |
| 325 | + border: none; |
28 | 326 | }
|
29 |
| -a { |
30 |
| - color: #42b983; |
| 327 | +hr { |
| 328 | + border-top: 1px dashed #ABABAB; |
31 | 329 | }
|
32 | 330 | </style>
|
0 commit comments