// P0/PRF Registration API // Stores wrapped L3 credentials in admin DB for POP distribution package main import ( "encoding/base64" "encoding/json" "fmt" "net/http" "strconv" "time" ) // CredentialRegisterRequest - what the client sends when enrolling a device type CredentialRegisterRequest struct { // Identity CustomerID string `json:"customer_id"` // Who owns this credential VaultL0 string `json:"vault_l0"` // Which vault this opens // WebAuthn data CredentialID string `json:"credential_id"` // base64 encoded WebAuthn credential ID PublicKey string `json:"public_key"` // base64 encoded public key (for signature verification) // PRF/P0 data P0 string `json:"p0"` // First 4 bytes of PRF, hex encoded (lookup key) PRFSalt string `json:"prf_salt"` // Salt used for PRF (should be "Clavitor") // Wrapped L3 (the actual vault key, encrypted with PRF) WrappedL3 string `json:"wrapped_l3"` // base64 encoded encrypted L3 // Metadata EnrolledBy string `json:"enrolled_by"` // Human ID who authorized this (if adding 2nd device) DeviceName string `json:"device_name"` // "YubiKey 5", "iPhone Touch ID", etc. DeviceType string `json:"device_type"` // "cross-platform", "platform", "hybrid" } // CredentialRegisterResponse type CredentialRegisterResponse struct { Success bool `json:"success"` CredentialID int64 `json:"credential_id,omitempty"` // Our internal ID Error string `json:"error,omitempty"` } // RegisterCredential stores a new P0→WL3 mapping func handleRegisterCredential(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req CredentialRegisterRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondJSON(w, CredentialRegisterResponse{Success: false, Error: "invalid json: " + err.Error()}) return } // Validate required fields if req.CustomerID == "" || req.VaultL0 == "" || req.P0 == "" || req.WrappedL3 == "" { respondJSON(w, CredentialRegisterResponse{Success: false, Error: "missing required fields"}) return } // Verify customer exists var customerExists bool err := db.QueryRow("SELECT 1 FROM customers WHERE id = ?", req.CustomerID).Scan(&customerExists) if err != nil { respondJSON(w, CredentialRegisterResponse{Success: false, Error: "customer not found"}) return } // Check for P0 collision (should be rare but possible) var existingL0 string err = db.QueryRow("SELECT l0 FROM credentials WHERE p0 = ?", req.P0).Scan(&existingL0) if err == nil && existingL0 != req.VaultL0 { // P0 collision with different vault - this is a problem respondJSON(w, CredentialRegisterResponse{Success: false, Error: "P0 collision detected - different vault"}) return } // Generate WL3 storage path // Format: {first_2_chars_of_p0}/{full_p0}/{timestamp}_{random}.cla wl3Path := generateWL3Path(req.P0) // Generate our internal credential ID credID := generateInternalID() // Store in DB _, err = db.Exec(` INSERT INTO credentials ( id, customer_id, p0, l0, credential_id, public_key, wl3_path, enrolled_by, device_name, device_type, created_at, last_used_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, credID, req.CustomerID, req.P0, req.VaultL0, req.CredentialID, // Store original WebAuthn credential ID req.PublicKey, wl3Path, req.EnrolledBy, req.DeviceName, req.DeviceType, time.Now().Unix(), time.Now().Unix(), ) if err != nil { respondJSON(w, CredentialRegisterResponse{Success: false, Error: "database error: " + err.Error()}) return } // Store the actual wrapped L3 content // In production this would write to the WORM filesystem // For now we store it in a separate table or as a blob wrappedL3Bytes, _ := base64.StdEncoding.DecodeString(req.WrappedL3) err = storeWL3Content(wl3Path, wrappedL3Bytes) if err != nil { // Rollback credential record db.Exec("DELETE FROM credentials WHERE id = ?", credID) respondJSON(w, CredentialRegisterResponse{Success: false, Error: "failed to store WL3: " + err.Error()}) return } // Log the event logEvent("credential_registered", req.CustomerID, req.VaultL0, map[string]interface{}{ "credential_id": credID, "p0": req.P0, "device_name": req.DeviceName, }) respondJSON(w, CredentialRegisterResponse{Success: true, CredentialID: credID}) } // ListCredentialsForCustomer - returns all credentials for a customer // Used by POPs to pull WL3s for their assigned customers func handleListCredentials(w http.ResponseWriter, r *http.Request) { customerID := r.URL.Query().Get("customer_id") if customerID == "" { http.Error(w, "customer_id required", http.StatusBadRequest) return } // TODO: Authenticate POP - must have valid agent token // For now open for Q&D rows, err := db.Query(` SELECT id, p0, l0, wl3_path, device_name, device_type, created_at FROM credentials WHERE customer_id = ? ORDER BY created_at DESC `, customerID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer rows.Close() var creds []map[string]interface{} for rows.Next() { var id, p0, l0, path, deviceName, deviceType string var createdAt int64 rows.Scan(&id, &p0, &l0, &path, &deviceName, &deviceType, &createdAt) creds = append(creds, map[string]interface{}{ "id": id, "p0": p0, "l0": l0, "wl3_path": path, "device_name": deviceName, "device_type": deviceType, "created_at": createdAt, }) } respondJSON(w, creds) } // GetWL3Content - returns the actual wrapped L3 bytes // POPs call this to download WL3 for local storage func handleGetWL3(w http.ResponseWriter, r *http.Request) { p0 := r.URL.Query().Get("p0") if p0 == "" { http.Error(w, "p0 required", http.StatusBadRequest) return } // TODO: Authenticate POP with super duper secure auth // For now just return the content // Find the WL3 path var wl3Path string err := db.QueryRow("SELECT wl3_path FROM credentials WHERE p0 = ? ORDER BY created_at DESC LIMIT 1", p0).Scan(&wl3Path) if err != nil { http.Error(w, "credential not found", http.StatusNotFound) return } // Read and return content, err := readWL3Content(wl3Path) if err != nil { http.Error(w, "failed to read WL3: "+err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/octet-stream") w.Write(content) } // SyncStatus - returns credentials that need syncing to POPs // POPs poll this to know what to download func handleSyncStatus(w http.ResponseWriter, r *http.Request) { // Query params popID := r.URL.Query().Get("pop_id") lastSync := r.URL.Query().Get("last_sync") if popID == "" { http.Error(w, "pop_id required", http.StatusBadRequest) return } // TODO: Authenticate POP var lastSyncTime int64 if lastSync != "" { lastSyncTime, _ = strconv.ParseInt(lastSync, 10, 64) } // Return credentials created since last sync rows, err := db.Query(` SELECT id, customer_id, p0, l0, wl3_path, created_at FROM credentials WHERE created_at > ? ORDER BY created_at ASC LIMIT 1000 `, lastSyncTime) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer rows.Close() var toSync []map[string]interface{} for rows.Next() { var id, customerID, p0, l0, path string var createdAt int64 rows.Scan(&id, &customerID, &p0, &l0, &path, &createdAt) toSync = append(toSync, map[string]interface{}{ "id": id, "customer_id": customerID, "p0": p0, "l0": l0, "wl3_path": path, "created_at": createdAt, }) } // Update POP sync state _, _ = db.Exec(` INSERT INTO pop_sync_state (pop_id, last_sync_at, updated_at) VALUES (?, ?, ?) ON CONFLICT(pop_id) DO UPDATE SET last_sync_at = excluded.last_sync_at, updated_at = excluded.updated_at `, popID, time.Now().Unix(), time.Now().Unix()) respondJSON(w, map[string]interface{}{ "credentials": toSync, "server_time": time.Now().Unix(), }) } // TODO: SUPER DUPER SECURE POP AUTHENTICATION // This needs: // 1. mTLS - POP presents certificate signed by our CA // 2. Signed requests - each request signed with POP's private key // 3. Replay protection - nonce + timestamp // 4. IP allowlist - only known POP IPs // 5. Rate limiting per POP // 6. Audit logging of all POP access func handlePOPAuthMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // TODO: Implement super duper secure auth // For now just pass through for Q&D next(w, r) } } // Helper functions func generateWL3Path(p0 string) string { // P0 is hex, like "a1b2c3d4" // Path: a1/a1b2c3d4/20250405_7a8f9e2b.cla if len(p0) < 4 { p0 = fmt.Sprintf("%08s", p0) // Pad } shard := p0[:2] // First 2 chars for sharding now := time.Now() random := generateRandomHex(8) return fmt.Sprintf("%s/%s/%04d%02d%02d_%s.cla", shard, p0, now.Year(), now.Month(), now.Day(), random) } func generateInternalID() int64 { // Use timestamp + random for now // In production use lib.NewID() return time.Now().UnixNano() } func generateRandomHex(n int) string { bytes := make([]byte, n/2) // In real code: crypto/rand for i := range bytes { bytes[i] = byte(i) // Fake } return fmt.Sprintf("%x", bytes) } func storeWL3Content(path string, content []byte) error { // TODO: Write to WORM filesystem // For now store in a simple table _, err := db.Exec(` INSERT OR REPLACE INTO wl3_storage (path, content, created_at) VALUES (?, ?, ?) `, path, content, time.Now().Unix()) return err } func readWL3Content(path string) ([]byte, error) { // TODO: Read from WORM filesystem // For now read from table var content []byte err := db.QueryRow("SELECT content FROM wl3_storage WHERE path = ?", path).Scan(&content) return content, err } func logEvent(eventType, customerID, vaultL0 string, details map[string]interface{}) { detailsJSON, _ := json.Marshal(details) db.Exec(` INSERT INTO events (occurred_at, event_type, entity_type, entity_id, customer_id, details, source) VALUES (?, ?, ?, ?, ?, ?, ?) `, time.Now().Unix(), eventType, "credential", "", customerID, string(detailsJSON), "api") }