the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Encapsulate SSH key helpers in useSshKeys hook

+102 -98
+102 -98
apps/web/src/hooks/useSshKeys.ts
··· 6 6 seedBase64: string; 7 7 }; 8 8 9 - function u32(n: number): Uint8Array { 10 - return new Uint8Array([ 11 - (n >>> 24) & 0xff, 12 - (n >>> 16) & 0xff, 13 - (n >>> 8) & 0xff, 14 - n & 0xff, 15 - ]); 16 - } 17 - 18 - function concatBytes(...arrays: Uint8Array[]): Uint8Array { 19 - const total = arrays.reduce((sum, arr) => sum + arr.length, 0); 20 - const out = new Uint8Array(total); 21 - let offset = 0; 22 - 23 - for (const arr of arrays) { 24 - out.set(arr, offset); 25 - offset += arr.length; 9 + export function useSshKeys() { 10 + function u32(n: number): Uint8Array { 11 + return new Uint8Array([ 12 + (n >>> 24) & 0xff, 13 + (n >>> 16) & 0xff, 14 + (n >>> 8) & 0xff, 15 + n & 0xff, 16 + ]); 26 17 } 27 18 28 - return out; 29 - } 19 + function concatBytes(...arrays: Uint8Array[]): Uint8Array { 20 + const total = arrays.reduce((sum, arr) => sum + arr.length, 0); 21 + const out = new Uint8Array(total); 22 + let offset = 0; 30 23 31 - function sshString(bytes: Uint8Array): Uint8Array { 32 - return concatBytes(u32(bytes.length), bytes); 33 - } 24 + for (const arr of arrays) { 25 + out.set(arr, offset); 26 + offset += arr.length; 27 + } 34 28 35 - function text(value: string): Uint8Array { 36 - return new TextEncoder().encode(value); 37 - } 29 + return out; 30 + } 38 31 39 - function randomU32(): number { 40 - const buf = new Uint32Array(1); 41 - crypto.getRandomValues(buf); 42 - return buf[0]!; 43 - } 32 + function sshString(bytes: Uint8Array): Uint8Array { 33 + return concatBytes(u32(bytes.length), bytes); 34 + } 44 35 45 - function wrapPem(label: string, bytes: Uint8Array): string { 46 - const base64 = btoa(String.fromCharCode(...bytes)); 47 - const lines = base64.match(/.{1,70}/g)?.join("\n") ?? base64; 48 - return `-----BEGIN ${label}-----\n${lines}\n-----END ${label}-----`; 49 - } 36 + function text(value: string): Uint8Array { 37 + return new TextEncoder().encode(value); 38 + } 50 39 51 - function buildEd25519PublicKeyBlob(publicKey: Uint8Array): Uint8Array { 52 - return concatBytes(sshString(text("ssh-ed25519")), sshString(publicKey)); 53 - } 40 + function randomU32(): number { 41 + const buf = new Uint32Array(1); 42 + crypto.getRandomValues(buf); 43 + return buf[0]!; 44 + } 54 45 55 - function buildOpenSSHEd25519PrivateKey( 56 - publicKey: Uint8Array, 57 - secretKey64: Uint8Array, // must be seed(32) || publicKey(32) 58 - comment: string, 59 - ): string { 60 - if (publicKey.length !== 32) { 61 - throw new Error("Invalid Ed25519 public key length"); 46 + function wrapPem(label: string, bytes: Uint8Array): string { 47 + const base64 = btoa(String.fromCharCode(...bytes)); 48 + const lines = base64.match(/.{1,70}/g)?.join("\n") ?? base64; 49 + return `-----BEGIN ${label}-----\n${lines}\n-----END ${label}-----`; 62 50 } 63 51 64 - if (secretKey64.length !== 64) { 65 - throw new Error("Invalid Ed25519 secret key length"); 52 + function buildEd25519PublicKeyBlob(publicKey: Uint8Array): Uint8Array { 53 + return concatBytes(sshString(text("ssh-ed25519")), sshString(publicKey)); 66 54 } 67 55 68 - const publicBlob = buildEd25519PublicKeyBlob(publicKey); 69 - const checkint = randomU32(); 70 - const commentBytes = text(comment); 56 + function buildOpenSSHEd25519PrivateKey( 57 + publicKey: Uint8Array, 58 + secretKey64: Uint8Array, // must be seed(32) || publicKey(32) 59 + comment: string, 60 + ): string { 61 + if (publicKey.length !== 32) { 62 + throw new Error("Invalid Ed25519 public key length"); 63 + } 71 64 72 - const privateSectionWithoutPadding = concatBytes( 73 - u32(checkint), 74 - u32(checkint), 75 - sshString(text("ssh-ed25519")), 76 - sshString(publicKey), 77 - sshString(secretKey64), 78 - sshString(commentBytes), 79 - ); 65 + if (secretKey64.length !== 64) { 66 + throw new Error("Invalid Ed25519 secret key length"); 67 + } 80 68 81 - const blockSize = 8; 82 - const remainder = privateSectionWithoutPadding.length % blockSize; 83 - const padLen = remainder === 0 ? 0 : blockSize - remainder; 69 + const publicBlob = buildEd25519PublicKeyBlob(publicKey); 70 + const checkint = randomU32(); 71 + const commentBytes = text(comment); 84 72 85 - const padding = new Uint8Array(padLen); 86 - for (let i = 0; i < padLen; i++) { 87 - padding[i] = i + 1; 88 - } 73 + const privateSectionWithoutPadding = concatBytes( 74 + u32(checkint), 75 + u32(checkint), 76 + sshString(text("ssh-ed25519")), 77 + sshString(publicKey), 78 + sshString(secretKey64), 79 + sshString(commentBytes), 80 + ); 89 81 90 - const privateSection = concatBytes(privateSectionWithoutPadding, padding); 82 + const blockSize = 8; 83 + const remainder = privateSectionWithoutPadding.length % blockSize; 84 + const padLen = remainder === 0 ? 0 : blockSize - remainder; 91 85 92 - const opensshKey = concatBytes( 93 - text("openssh-key-v1\0"), 94 - sshString(text("none")), // ciphername 95 - sshString(text("none")), // kdfname 96 - sshString(new Uint8Array()), // kdfoptions 97 - u32(1), // number of keys 98 - sshString(publicBlob), // public key 99 - sshString(privateSection), // private section 100 - ); 86 + const padding = new Uint8Array(padLen); 87 + for (let i = 0; i < padLen; i++) { 88 + padding[i] = i + 1; 89 + } 101 90 102 - return wrapPem("OPENSSH PRIVATE KEY", opensshKey); 103 - } 91 + const privateSection = concatBytes(privateSectionWithoutPadding, padding); 104 92 105 - export async function generateEd25519SSHKeyPair( 106 - comment = "user@browser", 107 - ): Promise<SSHKeyPair> { 108 - await sodium.ready; 93 + const opensshKey = concatBytes( 94 + text("openssh-key-v1\0"), 95 + sshString(text("none")), // ciphername 96 + sshString(text("none")), // kdfname 97 + sshString(new Uint8Array()), // kdfoptions 98 + u32(1), // number of keys 99 + sshString(publicBlob), // public key 100 + sshString(privateSection), // private section 101 + ); 109 102 110 - const seed = new Uint8Array(32); 111 - crypto.getRandomValues(seed); 103 + return wrapPem("OPENSSH PRIVATE KEY", opensshKey); 104 + } 112 105 113 - const kp = sodium.crypto_sign_seed_keypair(seed); 106 + async function generateEd25519SSHKeyPair( 107 + comment = "user@browser", 108 + ): Promise<SSHKeyPair> { 109 + await sodium.ready; 114 110 115 - const publicKey = new Uint8Array(kp.publicKey); 111 + const seed = new Uint8Array(32); 112 + crypto.getRandomValues(seed); 116 113 117 - const secretKey64 = concatBytes(seed, publicKey); 114 + const kp = sodium.crypto_sign_seed_keypair(seed); 118 115 119 - const publicBlob = buildEd25519PublicKeyBlob(publicKey); 116 + const publicKey = new Uint8Array(kp.publicKey); 120 117 121 - const publicKeyOpenSSH = `ssh-ed25519 ${sodium.to_base64(publicBlob, sodium.base64_variants.ORIGINAL)} ${comment}`; 118 + const secretKey64 = concatBytes(seed, publicKey); 122 119 123 - const privateKeyOpenSSH = buildOpenSSHEd25519PrivateKey( 124 - publicKey, 125 - secretKey64, 126 - comment, 127 - ); 120 + const publicBlob = buildEd25519PublicKeyBlob(publicKey); 121 + 122 + const publicKeyOpenSSH = `ssh-ed25519 ${sodium.to_base64(publicBlob, sodium.base64_variants.ORIGINAL)} ${comment}`; 123 + 124 + const privateKeyOpenSSH = buildOpenSSHEd25519PrivateKey( 125 + publicKey, 126 + secretKey64, 127 + comment, 128 + ); 129 + 130 + return { 131 + publicKey: publicKeyOpenSSH, 132 + privateKey: privateKeyOpenSSH, 133 + seedBase64: sodium.to_base64(seed, sodium.base64_variants.ORIGINAL), 134 + }; 135 + } 128 136 129 - return { 130 - publicKey: publicKeyOpenSSH, 131 - privateKey: privateKeyOpenSSH, 132 - seedBase64: sodium.to_base64(seed, sodium.base64_variants.ORIGINAL), 133 - }; 137 + return { generateEd25519SSHKeyPair }; 134 138 }