Skip to content

Commit

Permalink
feat: Add GLOBAL_REQUEST, DEBUG, IGNORE, aes256-ctr, aes192-ctr
Browse files Browse the repository at this point in the history
  • Loading branch information
Manaf941 committed Jul 22, 2024
1 parent 012415d commit 87628d7
Show file tree
Hide file tree
Showing 14 changed files with 404 additions and 36 deletions.
92 changes: 81 additions & 11 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ import ServiceRequest from "./packets/ServiceRequest.js"
import ServiceAccept from "./packets/ServiceAccept.js"
import Agent from "./publickey/Agent.js"
import NoneAgent from "./publickey/NoneAgent.js"
import GlobalRequest from "./packets/GlobalRequest.js"
import RequestFailure from "./packets/RequestFailure.js"
import Debug from "./packets/Debug.js"
import { readNextBuffer } from "./utils/Buffer.js"

export interface ClientOptions {
hostname: string
Expand Down Expand Up @@ -156,6 +160,7 @@ export default class Client extends (EventEmitter as new () => TypedEmitter<Clie

hasReceivedNewKeys: boolean = false
hasSentNewKeys: boolean = false
hasAuthenticated: boolean = false

state = SocketState.Closed
get isConnected(): boolean {
Expand Down Expand Up @@ -374,6 +379,56 @@ export default class Client extends (EventEmitter as new () => TypedEmitter<Clie
// we could not authenticate.
throw new Error("All authentication methods failed.")
}
this.hasAuthenticated = true

// now that we have received USERAUTH_SUCCESS, we need
// to handle GLOBAL_REQUEST.

this.on("packet", (packet) => {
if (!(packet instanceof GlobalRequest)) return

this.debug(`Received global request packet:`, packet)

switch (packet.data.request_name) {
case "[email protected]": {
const hostkeys = []
let raw = packet.data.args
while (raw.length != 0) {
let arg: Buffer
;[arg, raw] = readNextBuffer(raw)

try {
hostkeys.push(PublicKey.parse(arg))
} catch (err) {
// unsupported host key algorithm
// or parse error
// either way don't care and silently fail.
this.debug(`Error while trying to parse host key:`, err)
}
}

this.debug(`Received ${hostkeys.length} valid host keys`)

// Do we care ?
// at this point, most usage will be
// from people ignoring host keys
// TODO: need to implement verifying host keys

// https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD
// section 2.5 (ctrl + f search for "[email protected]")
break
}
default: {
this.debug(`Unknown global request name: ${packet.data.request_name}`)
if (packet.data.want_reply) {
// this might be a keep alive lol
// shitty spec
// either way, send a failure response.
this.sendPacket(new RequestFailure({}))
}
}
}
})
}

waitEvent<event extends keyof ClientEvents>(
Expand Down Expand Up @@ -622,33 +677,48 @@ export default class Client extends (EventEmitter as new () => TypedEmitter<Clie
}

const p = packet.parse(payload)
this.emit("packet", p)
this.debug("Parsing packet:", p)

this.emit("packet", p)

switch (packet.type) {
case SSHPacketType.SSH_MSG_DISCONNECT: {
const disconnect = p as Disconnect
this.debug(
"Server disconnected:",
DisconnectReason[disconnect.data.reason_code],
disconnect.data.description,
disconnect.data.language_tag,
)
// TODO: Handle disconnect
break
}

case SSHPacketType.SSH_MSG_IGNORE:
this.debug(`Received Ignore packet. Ignoring.`)
break

case SSHPacketType.SSH_MSG_DEBUG: {
const debug = p as Debug
this.debug(`Received debug packet:`, [debug.data.message])
break
}

case SSHPacketType.SSH_MSG_KEXINIT:
// handle key exchange
this.emit("serverKexInit", p as KexInit, payload)
break

case SSHPacketType.SSH_MSG_KEXDH_REPLY:
// handle key exchange
this.emit("serverKexDHReply", p as KexDHReply)
break

case SSHPacketType.SSH_MSG_NEWKEYS:
this.hasReceivedNewKeys = true
this.emit("serverNewKeys")
// handle key exchange
break
case SSHPacketType.SSH_MSG_DISCONNECT: {
const disconnect = p as Disconnect
this.debug(
"Server disconnected:",
DisconnectReason[disconnect.data.reason_code],
disconnect.data.description,
disconnect.data.language_tag,
)
// TODO: Handle disconnect
}
}

if (this.buffering.length > 0) {
Expand Down
16 changes: 12 additions & 4 deletions src/algorithms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import DiffieHellmanGroup15SHA512 from "./algorithms/kex/diffie-hellman-group15-
import DiffieHellmanGroup17SHA512 from "./algorithms/kex/diffie-hellman-group17-sha512.js"

import AES128CTR from "./algorithms/encryption/aes128-ctr.js"
import AES192CTR from "./algorithms/encryption/aes192-ctr.js"
import AES256CTR from "./algorithms/encryption/aes256-ctr.js"

//import HMACSHA2256 from "./algorithms/mac/hmac-sha2-256.js";
import HMACSHA1 from "./algorithms/mac/hmac-sha1.js"
Expand All @@ -24,12 +26,10 @@ export abstract class KexAlgorithm {
static requires_encryption: boolean
static requires_signature: boolean


constructor() {
throw new Error("Not implemented")
}


static instantiate(): KexAlgorithm {
throw new Error("Not implemented")
}
Expand Down Expand Up @@ -80,6 +80,8 @@ export abstract class EncryptionAlgorithm {
}
}
export const encryption_algorithms = new Map<string, typeof EncryptionAlgorithm>([
["aes256-ctr", AES256CTR],
["aes192-ctr", AES192CTR],
["aes128-ctr", AES128CTR],
])

Expand Down Expand Up @@ -189,7 +191,11 @@ export function chooseAlgorithms(client: Client | ServerClient) {
assert(client.hostKeyAlgorithm, "No host key algorithm found")
}

for (const alg of client.clientKexInit.data.encryption_algorithms_client_to_server) {
// TODO: Figure out why this needs a reverse
// I will rewrite this to be cleaner later.
for (const alg of [
...client.clientKexInit.data.encryption_algorithms_client_to_server,
].reverse()) {
if (!client.serverKexInit.data.encryption_algorithms_client_to_server.includes(alg)) {
continue
}
Expand All @@ -200,7 +206,9 @@ export function chooseAlgorithms(client: Client | ServerClient) {
client.clientEncryptionAlgorithm = algorithm
}
assert(client.clientEncryptionAlgorithm, "No client to server encryption algorithm found")
for (const alg of client.clientKexInit.data.encryption_algorithms_server_to_client) {
for (const alg of [
...client.clientKexInit.data.encryption_algorithms_server_to_client,
].reverse()) {
if (!client.serverKexInit.data.encryption_algorithms_server_to_client.includes(alg)) {
continue
}
Expand Down
21 changes: 3 additions & 18 deletions src/algorithms/encryption/aes128-ctr.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import crypto from "crypto"
import { EncryptionAlgorithm } from "../../algorithms.js"
import AESNCTR from "./aesN-ctr.js"

export default class AES128CTR implements EncryptionAlgorithm {
export default class AES128CTR extends AESNCTR {
static alg_name = "aes128-ctr"
static key_length = 16
static iv_length = 16
Expand All @@ -11,22 +11,7 @@ export default class AES128CTR implements EncryptionAlgorithm {
return new AES128CTR(key, iv)
}

key: Buffer
iv: Buffer
encrypt_instance: crypto.Cipher
decrypt_instance: crypto.Cipher
constructor(key: Buffer, iv: Buffer) {
this.key = key
this.iv = iv
this.encrypt_instance = crypto.createCipheriv("aes-128-ctr", this.key, this.iv)
this.decrypt_instance = crypto.createDecipheriv("aes-128-ctr", this.key, this.iv)
}

encrypt(plaintext: Buffer): Buffer {
return this.encrypt_instance.update(plaintext)
}

decrypt(ciphertext: Buffer): Buffer {
return this.decrypt_instance.update(ciphertext)
super("aes-128-ctr", key, iv)
}
}
17 changes: 17 additions & 0 deletions src/algorithms/encryption/aes192-ctr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { EncryptionAlgorithm } from "../../algorithms.js"
import AESNCTR from "./aesN-ctr.js"

export default class AES192CTR extends AESNCTR {
static alg_name = "aes192-ctr"
static key_length = 24
static iv_length = 16
static block_size = 16

static instantiate(key: Buffer, iv: Buffer): EncryptionAlgorithm {
return new AES192CTR(key, iv)
}

constructor(key: Buffer, iv: Buffer) {
super("aes-192-ctr", key, iv)
}
}
17 changes: 17 additions & 0 deletions src/algorithms/encryption/aes256-ctr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { EncryptionAlgorithm } from "../../algorithms.js"
import AESNCTR from "./aesN-ctr.js"

export default class AES256CTR extends AESNCTR {
static alg_name = "aes256-ctr"
static key_length = 32
static iv_length = 16
static block_size = 16

static instantiate(key: Buffer, iv: Buffer): EncryptionAlgorithm {
return new AES256CTR(key, iv)
}

constructor(key: Buffer, iv: Buffer) {
super("aes-256-ctr", key, iv)
}
}
27 changes: 27 additions & 0 deletions src/algorithms/encryption/aesN-ctr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import crypto from "crypto"
import { EncryptionAlgorithm } from "../../algorithms.js"

export default class AESNCTR implements EncryptionAlgorithm {
static key_length: number
static iv_length: number
static block_size: number

key: Buffer
iv: Buffer
encrypt_instance: crypto.Cipher
decrypt_instance: crypto.Cipher
constructor(algorithm: string, key: Buffer, iv: Buffer) {
this.key = key
this.iv = iv
this.encrypt_instance = crypto.createCipheriv(algorithm, this.key, this.iv)
this.decrypt_instance = crypto.createDecipheriv(algorithm, this.key, this.iv)
}

encrypt(plaintext: Buffer): Buffer {
return this.encrypt_instance.update(plaintext)
}

decrypt(ciphertext: Buffer): Buffer {
return this.decrypt_instance.update(ciphertext)
}
}
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export enum SSHPacketType {
SSH_MSG_USERAUTH_REQUEST = 50,
SSH_MSG_USERAUTH_FAILURE = 51,
SSH_MSG_USERAUTH_SUCCESS = 52,
// TODO: Support SSH_MSG_USERAUTH_BANNER
// Currently, if a server sends it, the connection will crash.
SSH_MSG_USERAUTH_BANNER = 53,

// This is messed up in the spec
Expand Down
5 changes: 2 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import Client from "./Client.js"
import DiskAgent from "./publickey/DiskAgent.js"

const client = new Client({
hostname: "127.0.0.1",
port: 1022,
hostname: "VPS1",
port: 22,
username: "debian",
password: "debian",
agent: new DiskAgent(),
})
client.on("debug", (...args) => console.debug(...args))
Expand Down
17 changes: 17 additions & 0 deletions src/packet.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { SSHPacketType } from "./constants.js"
import Debug from "./packets/Debug.js"
import Disconnect from "./packets/Disconnect.js"
import GlobalRequest from "./packets/GlobalRequest.js"
import Ignore from "./packets/Ignore.js"
import KexDHInit from "./packets/KexDHInit.js"
import KexDHReply from "./packets/KexDHReply.js"
import KexInit from "./packets/KexInit.js"
import NewKeys from "./packets/NewKeys.js"
import RequestFailure from "./packets/RequestFailure.js"
import RequestSuccess from "./packets/RequestSuccess.js"
import ServiceAccept from "./packets/ServiceAccept.js"
import ServiceRequest from "./packets/ServiceRequest.js"
import Unimplemented from "./packets/Unimplemented.js"
Expand Down Expand Up @@ -34,7 +39,9 @@ export default abstract class Packet {

export const packets = new Map<SSHPacketType, typeof Packet>([
[SSHPacketType.SSH_MSG_DISCONNECT, Disconnect],
[SSHPacketType.SSH_MSG_IGNORE, Ignore],
[SSHPacketType.SSH_MSG_UNIMPLEMENTED, Unimplemented],
[SSHPacketType.SSH_MSG_DEBUG, Debug],
[SSHPacketType.SSH_MSG_SERVICE_REQUEST, ServiceRequest],
[SSHPacketType.SSH_MSG_SERVICE_ACCEPT, ServiceAccept],

Expand All @@ -49,10 +56,16 @@ export const packets = new Map<SSHPacketType, typeof Packet>([
[SSHPacketType.SSH_MSG_USERAUTH_SUCCESS, UserAuthSuccess],

[SSHPacketType.SSH_MSG_USERAUTH_PK_OK, UserAuthPKOK],

[SSHPacketType.SSH_MSG_GLOBAL_REQUEST, GlobalRequest],
[SSHPacketType.SSH_MSG_REQUEST_FAILURE, RequestFailure],
[SSHPacketType.SSH_MSG_REQUEST_SUCCESS, RequestSuccess],
])
export interface PacketTypes {
[SSHPacketType.SSH_MSG_DISCONNECT]: Disconnect
[SSHPacketType.SSH_MSG_IGNORE]: Ignore
[SSHPacketType.SSH_MSG_UNIMPLEMENTED]: Unimplemented
[SSHPacketType.SSH_MSG_DEBUG]: Debug
[SSHPacketType.SSH_MSG_SERVICE_REQUEST]: ServiceRequest
[SSHPacketType.SSH_MSG_SERVICE_ACCEPT]: ServiceAccept

Expand All @@ -67,4 +80,8 @@ export interface PacketTypes {
[SSHPacketType.SSH_MSG_USERAUTH_SUCCESS]: UserAuthSuccess

[SSHPacketType.SSH_MSG_USERAUTH_PK_OK]: UserAuthPKOK

[SSHPacketType.SSH_MSG_GLOBAL_REQUEST]: GlobalRequest
[SSHPacketType.SSH_MSG_REQUEST_FAILURE]: RequestFailure
[SSHPacketType.SSH_MSG_REQUEST_SUCCESS]: RequestSuccess
}
Loading

0 comments on commit 87628d7

Please sign in to comment.