import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { EncryptionService } from './encryption.service'; import { CreateSshKeyDto } from './dto/create-ssh-key.dto'; import { UpdateSshKeyDto } from './dto/update-ssh-key.dto'; import { createHash } from 'crypto'; @Injectable() export class SshKeysService { constructor( private prisma: PrismaService, private encryption: EncryptionService, ) {} findAll(userId: number) { return this.prisma.sshKey.findMany({ where: { userId }, select: { id: true, name: true, keyType: true, fingerprint: true, publicKey: true, createdAt: true }, orderBy: { name: 'asc' }, }); } async findOne(id: number, userId?: number) { const key = await this.prisma.sshKey.findUnique({ where: { id }, include: { credentials: { select: { id: true, name: true } } }, }); if (!key) throw new NotFoundException(`SSH key ${id} not found`); if (userId !== undefined && key.userId !== userId) { throw new ForbiddenException('Access denied'); } // Never return encrypted private key over API return { id: key.id, name: key.name, keyType: key.keyType, fingerprint: key.fingerprint, publicKey: key.publicKey, credentials: key.credentials, createdAt: key.createdAt, }; } async create(userId: number, dto: CreateSshKeyDto) { // Detect key type from private key content const keyType = this.detectKeyType(dto.privateKey); // Generate fingerprint from public key if provided, else from private key const fingerprint = this.generateFingerprint(dto.publicKey || dto.privateKey); // Encrypt sensitive data const encryptedPrivateKey = this.encryption.encrypt(dto.privateKey); const passphraseEncrypted = dto.passphrase ? this.encryption.encrypt(dto.passphrase) : null; return this.prisma.sshKey.create({ data: { name: dto.name, keyType, fingerprint, publicKey: dto.publicKey || null, userId, encryptedPrivateKey, passphraseEncrypted, }, }); } async update(id: number, userId: number, dto: UpdateSshKeyDto) { const key = await this.prisma.sshKey.findUnique({ where: { id } }); if (!key) throw new NotFoundException(`SSH key ${id} not found`); if (key.userId !== userId) throw new ForbiddenException('Access denied'); const data: any = {}; if (dto.name) data.name = dto.name; if (dto.passphrase !== undefined) { data.passphraseEncrypted = dto.passphrase ? this.encryption.encrypt(dto.passphrase) : null; } return this.prisma.sshKey.update({ where: { id }, data }); } async remove(id: number, userId: number) { const key = await this.prisma.sshKey.findUnique({ where: { id } }); if (!key) throw new NotFoundException(`SSH key ${id} not found`); if (key.userId !== userId) throw new ForbiddenException('Access denied'); return this.prisma.sshKey.delete({ where: { id } }); } private detectKeyType(privateKey: string): string { if (privateKey.includes('RSA')) return 'rsa'; if (privateKey.includes('EC')) return 'ecdsa'; if (privateKey.includes('OPENSSH')) return 'ed25519'; // OpenSSH format, likely ed25519 return 'unknown'; } private generateFingerprint(keyContent: string): string { try { const hash = createHash('sha256').update(keyContent.trim()).digest('base64'); return `SHA256:${hash}`; } catch { return 'unknown'; } } }