diff --git a/src/exec/QemuSystem.go b/src/exec/QemuSystem.go
index e8524aa9c31e9f072702eba3b68a342df209b6d9..cab58522388a2389f1d1525560ee99659a76a06d 100644
--- a/src/exec/QemuSystem.go
+++ b/src/exec/QemuSystem.go
@@ -38,7 +38,8 @@ func CheckQemuSystem() error {
 
 // Creates a qemu-system command.
 // The nic argument must be either "none" or "user".
-func NewQemuSystem(qgaSock string, cpus, ram int, nic, diskFile string, spicePort int, secretPwdFile, certDir string) (*exec.Cmd, error) {
+func NewQemuSystem(qgaSock string, cpus, ram int, nic string, usbDevs []string, diskFile string, spicePort int, secretPwdFile, certDir string) (*exec.Cmd, error) {
+    // Network config
     nicArgs := "none"
     if nic == "user" {
         addr, err := utils.RandMacAddress()
@@ -48,6 +49,35 @@ func NewQemuSystem(qgaSock string, cpus, ram int, nic, diskFile string, spicePor
         nicArgs = "user,mac="+addr+",model=virtio-net-pci"
     }
 
+    // If there are USB devices, generates the QEMU command line to support them
+    usb := []string{}
+
+    if len(usbDevs) > 0 {
+        // A qemu-xhci device is required
+        usb = append(usb, "-device qemu-xhci")
+
+        // Generate the USB devices' filter list, for instance:
+        // "-1:0x0781:0x5567:-1:1|-1:0x067b:0x2303:-1:1|-1:-1:-1:-1:0"
+        var filter strings.Builder
+        for _, dev := range usbDevs {
+            filter.WriteString("-1:")
+            ids := strings.Split(dev, ":")  // Extracts vendorID (vid) and productID (pid)
+            usbDev := "0x"+ids[0]+":0x"+ids[1]
+            filter.WriteString(usbDev)
+            filter.WriteString(":-1:1|")
+        }
+        filter.WriteString("-1:-1:-1:-1:0")
+
+        // Generate QEMU's arguments for the USB devices
+        for i, _ := range usbDevs {
+            idx := strconv.Itoa(i+1)
+            usb = append(usb, "-chardev")
+            usb = append(usb, "spicevmc,name=usbredir,id=usbredir"+idx)
+            usb = append(usb, "-device")
+            usb = append(usb, "usb-redir,filter='"+filter.String()+"',chardev=usbredir"+idx)
+        }
+    }
+
     // Remarks:
     // 1) This device is to allow copy/paste between guest & client
     //  -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0
@@ -57,7 +87,32 @@ func NewQemuSystem(qgaSock string, cpus, ram int, nic, diskFile string, spicePor
     //   - In the guest: mkdir shared && sudo mount -t 9p sharedfs shared
     // 4) For QEMU Guest Agent support:
     //  -device virtio-serial -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 -chardev socket,path=path_to_sock_file,server=on,wait=off,id=qga0
-    cmd := exec.Command(qemusystemBinary, "-enable-kvm", "-cpu", "host", "-smp", "cpus="+strconv.Itoa(cpus), "-m", strconv.Itoa(ram), "-drive", "file="+diskFile+",index=0,media=disk,format=qcow2,discard=unmap,detect-zeroes=unmap,if=virtio", "-vga", "virtio", "-device", "virtio-serial-pci", "-object", "secret,id=sec,file="+secretPwdFile+",format=base64", "-spice", "tls-port="+strconv.Itoa(spicePort)+",password-secret=sec,x509-dir="+certDir, "-device", "virtserialport,chardev=spicechannel0,name=com.redhat.spice.0", "-chardev", "spicevmc,id=spicechannel0,name=vdagent", "-nic", nicArgs, "-device", "virtio-serial", "-device", "virtserialport,chardev=qga0,name=org.qemu.guest_agent.0", "-chardev", "socket,path="+qgaSock+",server=on,wait=off,id=qga0")
 
+    // Enable KVM
+    args := []string{"-enable-kvm"}
+    // CPU
+    args = append(args, "-cpu", "host", "-smp", "cpus="+strconv.Itoa(cpus))
+    // RAM
+    args = append(args, "-m", strconv.Itoa(ram))
+    // Harddrive
+    args = append(args, "-drive", "file="+diskFile+",index=0,media=disk,format=qcow2,discard=unmap,detect-zeroes=unmap,if=virtio")
+    // Display
+    args = append(args, "-vga", "virtio")
+    // Virtio serial
+    args = append(args, "-device", "virtio-serial-pci")
+    // Spice
+    args = append(args, "-object", "secret,id=sec,file="+secretPwdFile+",format=base64", "-spice", "tls-port="+strconv.Itoa(spicePort)+",password-secret=sec,x509-dir="+certDir, "-device", "virtserialport,chardev=spicechannel0,name=com.redhat.spice.0", "-chardev", "spicevmc,id=spicechannel0,name=vdagent")
+    // Network
+    args = append(args, "-nic", nicArgs)
+    // QEMU Guest Agent
+    args = append(args, "-device", "virtio-serial", "-device", "virtserialport,chardev=qga0,name=org.qemu.guest_agent.0", "-chardev", "socket,path="+qgaSock+",server=on,wait=off,id=qga0")
+    // USB redirection
+    for _, u := range usb {
+        args = append(args, u)
+    }
+
+    //cmd := exec.Command(qemusystemBinary, "-enable-kvm", "-cpu", "host", "-smp", "cpus="+strconv.Itoa(cpus), "-m", strconv.Itoa(ram), "-drive", "file="+diskFile+",index=0,media=disk,format=qcow2,discard=unmap,detect-zeroes=unmap,if=virtio", "-vga", "virtio", "-device", "virtio-serial-pci", "-object", "secret,id=sec,file="+secretPwdFile+",format=base64", "-spice", "tls-port="+strconv.Itoa(spicePort)+",password-secret=sec,x509-dir="+certDir, "-device", "virtserialport,chardev=spicechannel0,name=com.redhat.spice.0", "-chardev", "spicevmc,id=spicechannel0,name=vdagent", "-nic", nicArgs, "-device", "virtio-serial", "-device", "virtserialport,chardev=qga0,name=org.qemu.guest_agent.0", "-chardev", "socket,path="+qgaSock+",server=on,wait=off,id=qga0", usb)
+
+    cmd := exec.Command(qemusystemBinary, args...)
     return cmd, nil
 }
diff --git a/src/router/routerVMs.go b/src/router/routerVMs.go
index 964d35a3151654a8c8be43ff3b8247e443058293..dfb38f58454463a2091280d315d01b42f1c40acb 100644
--- a/src/router/routerVMs.go
+++ b/src/router/routerVMs.go
@@ -181,8 +181,9 @@ func (r *RouterVMs)CreateVM(c echo.Context) error {
     type Parameters struct {
         Name string           `json:"name"       validate:"required,min=4,max=256"`
         Cpus int              `json:"cpus"       validate:"required,gte=1,lte=16"`
-        Ram int               `json:"ram"        validate:"required,gte=512,lte=32768"`  // in MB
-        Nic vms.NicType       `json:"nic"        validate:"required"`  // "none" or "user"
+        Ram int               `json:"ram"        validate:"required,gte=512,lte=32768"`
+        Nic vms.NicType       `json:"nic"        validate:"required`
+        UsbDevs []string      `json:"usbDevs"    validate:"required`
         TemplateID uuid.UUID  `json:"templateID" validate:"required"`
     }
     p := new(Parameters)
@@ -191,7 +192,7 @@ func (r *RouterVMs)CreateVM(c echo.Context) error {
     }
 
     // Creates a new VM from the client's parameters.
-    vm, err := vms.NewVM(user.Email, p.Name, p.Cpus, p.Ram, p.Nic, p.TemplateID, user.Email)
+    vm, err := vms.NewVM(user.Email, p.Name, p.Cpus, p.Ram, p.Nic, p.UsbDevs, p.TemplateID, user.Email)
     if err != nil {
         return echo.NewHTTPError(http.StatusBadRequest, err.Error())
     }
diff --git a/src/version/version.go b/src/version/version.go
index 45abd3ec7d26b8f1c8299766d0dad2dc7bbaf0ae..20b181ad02d2d4b5924a1d99582569c012b4d5e6 100644
--- a/src/version/version.go
+++ b/src/version/version.go
@@ -6,7 +6,7 @@ import (
 
 const (
     major  = 1
-    minor  = 5
+    minor  = 6
     bugfix = 0
 )
 
diff --git a/src/vms/vm.go b/src/vms/vm.go
index ac6ccf0d5a034f6990de0538122954511074e0fc..fcdbd4b672ac7ee5dc29746ed8f62f0777cf5f5a 100644
--- a/src/vms/vm.go
+++ b/src/vms/vm.go
@@ -5,6 +5,7 @@ import (
     "sync"
     "path"
     "errors"
+    "strings"
     "syscall"
     "net/mail"
     "io/ioutil"
@@ -13,8 +14,8 @@ import (
     "encoding/base64"
     "nexus-server/qga"
     "nexus-server/caps"
-    "nexus-server/paths"
     "nexus-server/exec"
+    "nexus-server/paths"
     "github.com/google/uuid"
     "github.com/go-playground/validator/v10"
     "github.com/sethvargo/go-password/password"
@@ -28,9 +29,10 @@ type (
         Name string           `json:"name"       validate:"required,min=2,max=256"`
         Cpus int              `json:"cpus"       validate:"required,gte=1,lte=16"`
         Ram int               `json:"ram"        validate:"required,gte=512,lte=32768"`  // in MB
-        Nic NicType           `json:"nic"        validate:"required"`  // "none" or "user"
+        Nic NicType           `json:"nic"        validate:"required"`
+        UsbDevs []string      `json:"usbDevs"    validate:"required"`
         TemplateID uuid.UUID  `json:"templateID" validate:"required"`
-        Access map[string]caps.Capabilities `json:"access"  validate:"required"`
+        Access map[string]caps.Capabilities `json:"access"`
 
         // None of the fields below are serialized to disk.
         dir string             // VM directory
@@ -40,44 +42,46 @@ type (
         mutex *sync.Mutex
     }
 
-    runStates struct {
-        State VMState
-        Pid int
-        Port int
-        Pwd string
-    }
-
-    VMState string
-    NicType string
-
     // VM fields to be serialized to disk.
     VMDiskSerialized struct {
         ID uuid.UUID           `json:"id"`
         Owner string           `json:"owner"`
         Name string            `json:"name"`
         Cpus int               `json:"cpus"`
-        Ram int                `json:"ram"`  // in MB
+        Ram int                `json:"ram"`
         Nic NicType            `json:"nic"`
+        UsbDevs []string       `json:"usbDevs"`
         TemplateID uuid.UUID   `json:"templateID"`
         Access map[string]caps.Capabilities `json:"access"`
     }
 
     // VM fields to be serialized over the network (sent to the client).
     VMNetworkSerialized struct {
-        ID uuid.UUID
-        Owner string
-        Name string
-        Cpus int
-        Ram int
-        Nic NicType
-        TemplateID uuid.UUID
-        Access map[string]caps.Capabilities
+        ID uuid.UUID           `json:"id"`
+        Owner string           `json:"owner"`
+        Name string            `json:"name"`
+        Cpus int               `json:"cpus"`
+        Ram int                `json:"ram"`
+        Nic NicType            `json:"nic"`
+        UsbDevs []string       `json:"usbDevs"`
+        TemplateID uuid.UUID   `json:"templateID"`
+        Access map[string]caps.Capabilities `json:"access"`
+        State VMState          `json:"state"`
+        Port int               `json:"port"`
+        Pwd string             `json:"pwd"`
+        DiskBusy bool          `json:"diskBusy"`
+    }
+
+    runStates struct {
         State VMState
+        Pid int
         Port int
         Pwd string
-        DiskBusy bool          // If true the VM's disk is busy (cannot be modified or deleted)
     }
 
+    VMState string  // see stateXXX defined below
+    NicType string  // see nicXXX defined below
+
     endOfExecCallback func(vm *VM)
 )
 
@@ -104,7 +108,7 @@ var passwordGen, _ = password.NewGenerator(&password.GeneratorInput{
 })
 
 // Creates a VM.
-func NewVM(creatorEmail string, name string, cpus, ram int, nic NicType, templateID uuid.UUID, owner string) (*VM, error) {
+func NewVM(creatorEmail string, name string, cpus, ram int, nic NicType, usbDevs []string, templateID uuid.UUID, owner string) (*VM, error) {
     vmID, err := uuid.NewRandom()
 
     if err != nil {
@@ -118,6 +122,7 @@ func NewVM(creatorEmail string, name string, cpus, ram int, nic NicType, templat
     vm.Cpus = cpus
     vm.Ram = ram
     vm.Nic = nic
+    vm.UsbDevs = usbDevs
     vm.TemplateID = templateID
     id := vmID.String()
     vm.dir = filepath.Join(vms.dir, id[0:3], id[3:6], id)
@@ -139,6 +144,7 @@ func (vm *VM)SerializeToDisk() VMDiskSerialized {
         Cpus: vm.Cpus,
         Ram: vm.Ram,
         Nic: vm.Nic,
+        UsbDevs: vm.UsbDevs,
         TemplateID: vm.TemplateID,
         Access: vm.Access,
     }
@@ -152,6 +158,7 @@ func (vm *VM)SerializeToNetwork() VMNetworkSerialized {
         Cpus: vm.Cpus,
         Ram: vm.Ram,
         Nic: vm.Nic,
+        UsbDevs: vm.UsbDevs,
         TemplateID: vm.TemplateID,
         Access: vm.Access,
         State: vm.Run.State,
@@ -180,6 +187,7 @@ func newEmptyVM() *VM {
         Cpus: 0,
         Ram: 0,
         Nic: "",
+        UsbDevs: []string{},
         TemplateID: uuid.Nil,
         Access: make(map[string]caps.Capabilities),
 
@@ -237,6 +245,10 @@ func (vm *VM)validate() error {
         return errors.New("Invalid nic value: "+string(vm.Nic))
     }
 
+    if err := validateUsbDevs(vm.UsbDevs); err != nil {
+        return err
+    }
+
     if err := validator.New().Struct(vm); err != nil {
         return err
     }
@@ -473,7 +485,7 @@ func (vm *VM)reboot() error {
 // Executes the VM in QEMU using the specified spice port and password.
 func (vm *VM)runQEMU(port int, pwd, pwdFile string, endofExecFn endOfExecCallback) error {
     pkiDir := paths.GetInstance().NexusPkiDir
-    cmd, err := exec.NewQemuSystem(vm.qgaSock, vm.Cpus, vm.Ram, string(vm.Nic), filepath.Join(vm.dir, vmDiskFile), port, pwdFile, pkiDir)
+    cmd, err := exec.NewQemuSystem(vm.qgaSock, vm.Cpus, vm.Ram, string(vm.Nic), vm.UsbDevs, filepath.Join(vm.dir, vmDiskFile), port, pwdFile, pkiDir)
     if err != nil {
         return err
     }
@@ -503,3 +515,33 @@ func (vm *VM)runQEMU(port int, pwd, pwdFile string, endofExecFn endOfExecCallbac
 func (vm *VM)resetStates() {
     vm.Run = runStates { State: stateStopped, Pid: 0, Port: 0, Pwd: "" }
 }
+
+// Validates and convert a string of USB devices of the form "1fc9:001d,067b:2303"
+// into a slice of string where each element is a string of the form "1fc9:001d".
+func validateUsbDevs(devs []string) error {
+    for _, dev := range devs {
+        ids := strings.Split(dev, ":")  // Extracts vendorID (vid) and productID (pid)
+        if len(ids) != 2 {  // expect exactly two values: vid and pid
+            return errors.New("Invalid USB device syntax")
+        }
+        vid, pid := ids[0], ids[1]
+        if !isUsbId(vid) || !isUsbId(pid) {
+            return errors.New("Invalid USB vendor/product ID")
+        }
+    }
+    return nil
+}
+
+// A USB ID is either a vendor ID or a product ID.
+// It's a string containing exactly 4 hexadecimal digits.
+func isUsbId(s string) (bool) {
+    if len(s) != 4 {
+        return false
+    }
+    for _, r := range s {
+        if !(r >= '0' && r <= '9' || r >= 'a' && r <= 'f' || r >= 'A' && r <= 'F') {
+            return false
+        }
+    }
+    return true
+}