Files
David Garcia f60a3bdfdc Persistence: fresh Entra device join (DRS)
Native implementation of the DRS wire protocol - joins a fresh fake
device to Entra and persists the returned deviceId + X.509 device
cert. Cert-based persistence survives password reset.

- shared/DeviceCryptoHelper.{h,cpp}: pure-OpenSSL asymmetric
  primitives (Qt exposes none of these).
    generateRsa2048()      RSA-2048 keypair, PKCS#8 PEM out.
    buildCsr()             PKCS#10 CSR signed with SHA-256, PEM out.
    csrPemToDrsBase64()    strip PEM armor for the DRS enroll body.
    signJwtRS256()         compact JWS serialization for cert-bound
                           client_assertion (used by follow-up PRT
                           mint; not needed for the join itself).
    certSha256Thumbprint() hex uppercase, for x5t#S256 + UI display.
- shared/DeviceStore.{h,cpp}: sibling of SessionPersistence. Stores
  {deviceId, tenantId, displayName, thumbprint, joinedAt,
   privateKeyPem, deviceCertPem, transportKeyPem}. Private key +
  cert bytes are AES-256-GCM encrypted at rest via CryptoHelper,
  keyed by the same machine-derived default password. File:
  data/device_certs.dat.
- client/DeviceJoinWindow.{h,cpp} (Persistence menu): inherits
  EnumerationWindowBase. Operator pastes a DRS-audience token
  (client-id 01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9, resource
  urn:ms-drs:enterpriseregistration.windows.net), fills device
  details, submits. On success renders deviceId + thumbprint, adds
  a row to the stored table. Per-row: export cert PEM (public),
  export private key PEM (red confirm dialog), remove local record.
- Wired at Persistence menu between "Auth Methods" and "Windows
  Hello Attack". No CMake changes - both new .cpp files live in
  shared/ which globs *.cpp; OpenSSL is already linked PUBLIC
  through shared and transitively available in client.
2026-07-28 15:04:40 -06:00

62 lines
2.2 KiB
C++

#ifndef DEVICESTORE_H
#define DEVICESTORE_H
#include <QByteArray>
#include <QDateTime>
#include <QList>
#include <QString>
// DeviceStore - encrypted on-disk store for registered Entra devices.
//
// Sibling of SessionPersistence (**[4.2]**) that saves the material we
// obtained from a successful DRS join: the RSA private key that signed
// the CSR, the returned device X.509 cert, the deviceId + tenantId
// they're bound to, and metadata for later PRT-mint.
//
// All fields except the metadata are encrypted at rest via CryptoHelper
// (AES-256-GCM), keyed by the same machine-derived default password
// SessionPersistence uses (overridable via setEncryptionKey).
//
// Storage lives at `data/device_certs.dat` alongside `saved_sessions.dat`.
class DeviceStore {
public:
struct Device {
QString deviceId; // Entra deviceId (returned by DRS)
QString tenantId; // TenantID the device is joined to
QString displayName; // User-provided name shown in the portal
QString certThumbprint; // SHA256 hex, UPPER (from DeviceCryptoHelper)
QDateTime joinedAt; // When the join succeeded
QByteArray privateKeyPem; // Encrypted at rest; RSA that signed the CSR
QByteArray deviceCertPem; // Encrypted at rest; signed X.509 from DRS
QByteArray transportKeyPem; // Optional: private key of the transport pair
};
static DeviceStore& instance();
// Persist / retrieve.
bool saveDevice(const Device &d);
bool removeDevice(const QString &deviceId);
QList<Device> loadDevices();
Device findByDeviceId(const QString &deviceId);
bool clearAll();
// Same encryption-key contract as SessionPersistence.
void setEncryptionKey(const QString &key);
bool hasEncryptionKey() const;
private:
DeviceStore();
~DeviceStore() = default;
QString m_encryptionKey;
QString m_storagePath;
QString encrypt(const QByteArray &plaintext) const; // base64(JSON envelope)
QByteArray decrypt(const QString &ciphertext) const; // empty on failure
bool saveToFile(const QList<Device> &devices);
QList<Device> loadFromFile();
};
#endif // DEVICESTORE_H