f1e05c0ca3
Сканирование смонтированных USB-носителей (/run/media/$USER, /media, /mnt) на папки вида name.000 с *.key — это «контейнер КриптоПро на флешке». В шаге 3 wizard'а и в /admin/setup появилась таблица найденных контейнеров с кнопкой «Скопировать в локальное хранилище» (копирует папку в /var/opt/cprocsp/keys/$USER/, после чего контейнер виден как \\.\HDIMAGE\name и работает без вставленной флешки). Дедуп по AlreadyImported — если папка уже есть в HDIMAGE, кнопка не показывается. Сертификат из контейнера импортируется отдельно через certmgr -inst -cont (это пока вне UI, подсказка в текст flash-сообщения). Кроме того — переписан help-блок в шаге 3 wizard'a на основании «Инструккия M2M.pdf» (стр. 11, 16-19): явно расписано, что в режиме ИШ подписывает сам ИШ (наш ключ в ИШ), а в режиме прямого ONYX — bj-server. Таблица «что куда грузить»: УЦ МБ в mroot, УЦ НРД в mroot+uRoot, наш сертификат в uMy только если без ИШ. Сертификаты с Рутокена явно отмечены как «не грузить — сами появятся».
191 lines
6.5 KiB
Go
191 lines
6.5 KiB
Go
package lkgateway
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"os"
|
||
"os/user"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// FlashContainer — найденный на смонтированной флешке контейнер КриптоПро.
|
||
// КриптоПро CSP под Linux ожидает контейнер в виде папки <name>.000 с
|
||
// файлами header.key/masks.key/name.key/primary.key/primary2.key.
|
||
type FlashContainer struct {
|
||
// Mountpoint — путь смонтированной флешки, например /run/media/user/USB.
|
||
Mountpoint string
|
||
// Path — полный путь до папки <name>.000.
|
||
Path string
|
||
// Name — имя контейнера (без суффикса .000).
|
||
Name string
|
||
// Files — список файлов в контейнере (для дисплея).
|
||
Files []string
|
||
// AlreadyImported — true, если папка <name>.000 уже есть в локальном
|
||
// хранилище /var/opt/cprocsp/keys/<user>/.
|
||
AlreadyImported bool
|
||
}
|
||
|
||
// scanFlashContainers ищет контейнеры формата <name>.000 на типичных
|
||
// точках монтирования USB-носителей в Linux: /run/media/<user>/* и
|
||
// /media/<user>/* и /media/*. Возвращает список найденных контейнеров.
|
||
func scanFlashContainers() []FlashContainer {
|
||
u, err := user.Current()
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
roots := []string{
|
||
filepath.Join("/run/media", u.Username),
|
||
filepath.Join("/media", u.Username),
|
||
"/media",
|
||
"/mnt",
|
||
}
|
||
localKeysDir := filepath.Join("/var/opt/cprocsp/keys", u.Username)
|
||
|
||
var out []FlashContainer
|
||
for _, root := range roots {
|
||
entries, err := os.ReadDir(root)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
for _, e := range entries {
|
||
if !e.IsDir() {
|
||
continue
|
||
}
|
||
mountpoint := filepath.Join(root, e.Name())
|
||
out = append(out, findContainersAt(mountpoint, localKeysDir)...)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
func findContainersAt(mountpoint, localKeysDir string) []FlashContainer {
|
||
var out []FlashContainer
|
||
// Ищем папки <name>.000 на верхнем уровне и на 1 уровне вглубь.
|
||
_ = filepath.Walk(mountpoint, func(p string, info os.FileInfo, err error) error {
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
// Глубже 2 уровней не лезем (на флешке могут быть личные папки).
|
||
rel, _ := filepath.Rel(mountpoint, p)
|
||
if strings.Count(rel, string(filepath.Separator)) > 2 {
|
||
return filepath.SkipDir
|
||
}
|
||
if !info.IsDir() || !strings.HasSuffix(strings.ToLower(p), ".000") {
|
||
return nil
|
||
}
|
||
// Проверяем, что внутри лежат файлы вида *.key.
|
||
entries, _ := os.ReadDir(p)
|
||
var files []string
|
||
hasKey := false
|
||
for _, ent := range entries {
|
||
files = append(files, ent.Name())
|
||
if strings.HasSuffix(strings.ToLower(ent.Name()), ".key") {
|
||
hasKey = true
|
||
}
|
||
}
|
||
if !hasKey {
|
||
return nil
|
||
}
|
||
name := strings.TrimSuffix(filepath.Base(p), ".000")
|
||
fc := FlashContainer{
|
||
Mountpoint: mountpoint,
|
||
Path: p,
|
||
Name: name,
|
||
Files: files,
|
||
}
|
||
// Проверка: уже скопирован в локальное хранилище?
|
||
if _, err := os.Stat(filepath.Join(localKeysDir, name+".000")); err == nil {
|
||
fc.AlreadyImported = true
|
||
}
|
||
out = append(out, fc)
|
||
return filepath.SkipDir
|
||
})
|
||
return out
|
||
}
|
||
|
||
// copyContainerToLocal копирует папку <name>.000 с флешки в локальное
|
||
// хранилище КриптоПро /var/opt/cprocsp/keys/<user>/<name>.000. После
|
||
// этого контейнер виден как \\.\HDIMAGE\<name> и работает даже без
|
||
// вставленной флешки.
|
||
func copyContainerToLocal(srcDir string) (string, error) {
|
||
u, err := user.Current()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
localKeysDir := filepath.Join("/var/opt/cprocsp/keys", u.Username)
|
||
if err := os.MkdirAll(localKeysDir, 0o700); err != nil {
|
||
return "", fmt.Errorf("создать %s: %w", localKeysDir, err)
|
||
}
|
||
base := filepath.Base(srcDir)
|
||
dstDir := filepath.Join(localKeysDir, base)
|
||
if _, err := os.Stat(dstDir); err == nil {
|
||
return "", fmt.Errorf("контейнер %s уже существует в локальном хранилище", dstDir)
|
||
}
|
||
if err := os.MkdirAll(dstDir, 0o700); err != nil {
|
||
return "", fmt.Errorf("создать %s: %w", dstDir, err)
|
||
}
|
||
entries, err := os.ReadDir(srcDir)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
for _, e := range entries {
|
||
if e.IsDir() {
|
||
continue
|
||
}
|
||
src, err := os.Open(filepath.Join(srcDir, e.Name()))
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
dst, err := os.OpenFile(filepath.Join(dstDir, e.Name()),
|
||
os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
|
||
if err != nil {
|
||
src.Close()
|
||
return "", err
|
||
}
|
||
if _, err := io.Copy(dst, src); err != nil {
|
||
src.Close()
|
||
dst.Close()
|
||
return "", err
|
||
}
|
||
src.Close()
|
||
dst.Close()
|
||
}
|
||
return dstDir, nil
|
||
}
|
||
|
||
// copyContainer — POST /admin/setup/crypto/copy-container.
|
||
// Параметр src — путь до папки <name>.000 на флешке.
|
||
func (h *setupHandlers) copyContainer(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodPost {
|
||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
src := strings.TrimSpace(r.FormValue("src"))
|
||
if src == "" {
|
||
setupFlash(w, r, "Копирование контейнера: не указан путь")
|
||
return
|
||
}
|
||
// Минимальная защита: ожидаем .000 в конце пути.
|
||
if !strings.HasSuffix(strings.ToLower(src), ".000") {
|
||
setupFlash(w, r, "Копирование контейнера: путь должен заканчиваться на .000")
|
||
return
|
||
}
|
||
if _, err := os.Stat(src); err != nil {
|
||
setupFlash(w, r, "Копирование контейнера: исходная папка недоступна: "+err.Error())
|
||
return
|
||
}
|
||
dst, err := copyContainerToLocal(src)
|
||
if err != nil {
|
||
setupFlash(w, r, "Копирование контейнера: "+err.Error())
|
||
return
|
||
}
|
||
// Дадим CSP несколько мс «заметить» новый контейнер (не критично).
|
||
_, cancel := context.WithTimeout(r.Context(), 100*time.Millisecond)
|
||
cancel()
|
||
setupFlash(w, r, "Контейнер скопирован в "+dst+". Теперь он виден как \\\\.\\HDIMAGE\\"+strings.TrimSuffix(filepath.Base(dst), ".000")+" и работает без вставленной флешки. Импортируйте сертификат: certmgr -inst -cont '\\\\.\\HDIMAGE\\"+strings.TrimSuffix(filepath.Base(dst), ".000")+"' -store uMy.")
|
||
}
|