OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-registry / lib / identity.js

231 lines · javascript ref:
/**
 * Multi-Device Identity Management for Hyper-Registry
 *
 * STATUS: Implementation sketch - not yet functional
 */

import crypto from 'hypercore-crypto'
// import IdentityKey from 'keet-identity-key'

/**
 * Device identity management
 */
export class DeviceIdentity {
  constructor(options = {}) {
    this.storagePath = options.storagePath
    this.deviceKey = null
    this.deviceSecret = null
    this.deviceName = options.deviceName || null
  }

  /**
   * Initialize device identity
   * Creates or loads device keypair
   */
  async init() {
    // TODO: Load from secure storage if exists
    // TODO: Generate new if not exists

    const keypair = crypto.keyPair()
    this.deviceKey = keypair.publicKey
    this.deviceSecret = keypair.secretKey
  }

  /**
   * Get device public key
   */
  getPublicKey() {
    return this.deviceKey.toString('hex')
  }

  /**
   * Sign data with device key
   */
  sign(data) {
    const dataBuffer = typeof data === 'string' ? Buffer.from(data) : data
    return crypto.sign(dataBuffer, this.deviceSecret)
  }

  /**
   * Verify signature from another device
   */
  static verify(data, signature, publicKey) {
    const dataBuffer = typeof data === 'string' ? Buffer.from(data) : data
    const sigBuffer = typeof signature === 'string' ? Buffer.from(signature, 'hex') : signature
    const keyBuffer = typeof publicKey === 'string' ? Buffer.from(publicKey, 'hex') : publicKey

    return crypto.verify(dataBuffer, sigBuffer, keyBuffer)
  }
}

/**
 * Root identity (mnemonic-derived)
 */
export class RootIdentity {
  constructor(mnemonic = null) {
    this.mnemonic = mnemonic
    this.publicKey = null
    this.secretKey = null
  }

  /**
   * Generate new root identity with mnemonic
   */
  static generate() {
    // TODO: Use keet-identity-key
    // const identity = IdentityKey.generate()
    // return new RootIdentity(identity.mnemonic)

    // Placeholder: just generate a keypair
    const keypair = crypto.keyPair()
    const instance = new RootIdentity('placeholder-mnemonic-not-real')
    instance.publicKey = keypair.publicKey
    instance.secretKey = keypair.secretKey
    return instance
  }

  /**
   * Recover root identity from mnemonic
   */
  static fromMnemonic(mnemonic) {
    // TODO: Use keet-identity-key
    // const identity = new IdentityKey(mnemonic)
    // const instance = new RootIdentity(mnemonic)
    // instance.publicKey = identity.publicKey
    // instance.secretKey = identity.secretKey
    // return instance

    throw new Error('Not implemented - requires keet-identity-key')
  }

  /**
   * Get root public key
   */
  getPublicKey() {
    return this.publicKey.toString('hex')
  }

  /**
   * Sign device authorization
   */
  signDeviceAuth(device) {
    const data = JSON.stringify({
      key: device.key,
      name: device.name,
      canAuthorize: device.canAuthorize,
      canRevoke: device.canRevoke,
      resources: device.resources
    })

    return crypto.sign(Buffer.from(data), this.secretKey).toString('hex')
  }

  /**
   * Verify device was authorized by root
   */
  static verifyDeviceAuth(device, signature, rootPublicKey) {
    const data = JSON.stringify({
      key: device.key,
      name: device.name,
      canAuthorize: device.canAuthorize,
      canRevoke: device.canRevoke,
      resources: device.resources
    })

    return crypto.verify(
      Buffer.from(data),
      Buffer.from(signature, 'hex'),
      Buffer.from(rootPublicKey, 'hex')
    )
  }
}

/**
 * Device pairing protocol
 */
export class DevicePairing {
  constructor(registry) {
    this.registry = registry
    this.pendingRequests = new Map()
  }

  /**
   * Generate pairing code for new device
   */
  generatePairingCode(deviceKey) {
    const timestamp = Date.now()
    const data = `${deviceKey}:${timestamp}`
    const hash = crypto.hash(Buffer.from(data))
    const shortCode = hash.toString('hex').slice(0, 8).toUpperCase()

    // Format: XXXX-XXXX
    const formatted = shortCode.slice(0, 4) + '-' + shortCode.slice(4, 8)

    this.pendingRequests.set(shortCode, {
      deviceKey,
      timestamp,
      expiresAt: timestamp + 300000 // 5 minutes
    })

    return {
      code: formatted,
      deviceKey,
      expiresAt: timestamp + 300000
    }
  }

  /**
   * Verify pairing code and get device key
   */
  verifyPairingCode(code) {
    const normalized = code.replace('-', '').toUpperCase()

    const request = this.pendingRequests.get(normalized)
    if (!request) {
      return null
    }

    if (Date.now() > request.expiresAt) {
      this.pendingRequests.delete(normalized)
      return null
    }

    return request.deviceKey
  }

  /**
   * Complete pairing (authorize the device)
   */
  async completePairing(code, options = {}) {
    const deviceKey = this.verifyPairingCode(code)
    if (!deviceKey) {
      throw new Error('Invalid or expired pairing code')
    }

    // Authorize the device
    await this.registry.authorizeDevice(deviceKey, {
      name: options.name || 'new-device',
      canAuthorize: options.canAuthorize || false,
      canRevoke: options.canRevoke || false,
      resources: options.resources || null
    })

    // Clean up
    this.pendingRequests.delete(code.replace('-', '').toUpperCase())

    return { deviceKey, authorized: true }
  }

  /**
   * Clean up expired requests
   */
  cleanup() {
    const now = Date.now()
    for (const [code, request] of this.pendingRequests) {
      if (now > request.expiresAt) {
        this.pendingRequests.delete(code)
      }
    }
  }
}