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.

Refactor SSH key generation and fix PEM encoding

Use sodium.to_base64 for binary-safe base64 encoding and add a
trailing newline to PEM output. Extract publicLineFromPublicKey for
openSSH public lines, make buildOpenSSHEd25519PrivateKey accept a 32
byte seed (construct privateKey64 internally), inline randomU32 with
crypto.getRandomValues, and apply small formatting/cleanup changes.

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