Passbolt CE API авторизация: пошаговое решение ошибки
При интеграции с Passbolt Community Edition через API разработчики часто сталкиваются с ошибкой «Учетные данные недействительны» на этапе JWT-логина. Проблема кроется в несоответствии fingerprint ключа, переданного в запросе, и того, который сервер ожидает после проверки подписи challenge. В этой статье мы разберём типичную ситуацию, когда используется серверный GPG-ключ вместо ключа пользователя, и покажем, как правильно настроить аутентификацию.
Почему возникает ошибка «Учетные данные недействительны»?
Passbolt использует асимметричное шифрование: сервер отправляет challenge (случайную строку, закодированную как fingerprint), клиент должен подписать его своим приватным ключом и отправить обратно. Если fingerprint, который сервер получил при проверке подписи, не совпадает с переданным в теле запроса keyid, авторизация отклоняется. В вашем случае сервер ожидает fingerprint A505F33A1191319E787D0B2E5E92BA8A2C424B49, а вы передаёте fingerprint пользовательского ключа 999AAF9999E99999DC999A99CD99B990DE999999.
Какой ключ использовать для авторизации в Passbolt API?
Для корректной работы через API нужно использовать приватный ключ того пользователя, от имени которого вы хотите авторизоваться. Серверный ключ (server_private_key.asc) предназначен для административных задач и расшифровки данных на стороне сервера, но не для JWT-логина от имени конкретного пользователя. Если вы передаёте серверный ключ, то fingerprint совпадает, но сервер не находит соответствующего пользователя - отсюда ошибка.
Пошаговое исправление: от генерации до подписи
1. Загрузите правильный приватный ключ пользователя
Убедитесь, что в переменной окружения PRIVATE_KEY указан путь к ключу конкретного пользователя, а не сервера. Ключ пользователя экспортируется из Passbolt через веб-интерфейс (секция «Мой аккаунт» → «Ключи»).
2. Расшифруйте ключ, если он защищён паролем
В вашем коде расшифровка реализована верно: проверяете isDecrypted() и, если нужно, вызываете openpgp.decryptKey с passphrase. Для пользовательских ключей, созданных через интерфейс, passphrase обязателен.
3. Получите challenge корректно
Запрос GET /auth/verify.json?username=... возвращает fingerprint, который соответствует публичному ключу пользователя на сервере. Если вы загрузили приватный ключ другого пользователя, fingerprint не совпадёт - это нормально, но нужно убедиться, что username в запросе совпадает с владельцем ключа.
4. Подпишите challenge тем же ключом
Функция signChallenge использует расшифрованный ключ для подписи строки challenge. Важно: подпись должна быть detached (откреплённой) и в бинарном формате, как в вашем коде. После подписи конвертируйте результат в base64.
5. Передайте правильный fingerprint в JWT-запросе
В теле запроса keyid должен быть равен fingerprint того же ключа, которым вы подписывали. Его можно получить из переменной decryptedKey.getFingerprint() после расшифровки. Не используйте fingerprint из ответа сервера - он может отличаться, если вы ошиблись с пользователем.
Исправленный пример кода
const fs = require('fs');
const openpgp = require('openpgp');
const axios = require('axios');
require('dotenv').config();
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const PASSPHRASE = process.env.PASSPHRASE;
const EMAIL = process.env.EMAIL;
const API_URL = process.env.API_URL;
async function loadPrivateKey() {
console.log('[2] Загружаем приватный ключ...');
const privateKeyArmored = fs.readFileSync(PRIVATE_KEY, 'utf8');
const privateKey = await openpgp.readPrivateKey({ armoredKey: privateKeyArmored });
if (privateKey.isDecrypted()) {
console.log('[INFO] Ключ уже расшифрован');
return privateKey;
}
const decryptedKey = await openpgp.decryptKey({ privateKey, passphrase: PASSPHRASE });
console.log('[INFO] Ключ расшифрован через passphrase');
return decryptedKey;
}
async function getChallenge() {
console.log('[1] Получаем challenge...');
const res = await axios.get(`${API_URL}/auth/verify.json?username=${encodeURIComponent(EMAIL)}`);
return res.data.body.fingerprint;
}
async function signChallenge(challenge, decryptedKey) {
console.log('[3] Подписываем challenge...');
const message = await openpgp.createMessage({ text: challenge });
const detachedSignature = await openpgp.sign({
message,
signingKeys: decryptedKey,
detached: true,
format: 'binary'
});
return Buffer.from(detachedSignature).toString('base64');
}
async function loginJWT(fingerprint, signatureBase64) {
console.log('[4] Выполняем JWT авторизацию...');
try {
const response = await axios.post(`${API_URL}/auth/jwt/login.json`, {
gpg_auth: {
keyid: fingerprint,
signature: signatureBase64
}
});
console.log('[✅ Успешный ответ]:', response.data);
} catch (err) {
if (err.response) {
console.error('[Ошибка от сервера]:', err.response.data);
} else {
console.error('[Ошибка]:', err.message);
}
}
}
(async () => {
const privateKey = await loadPrivateKey();
const userFingerprint = privateKey.getFingerprint(); // Получаем fingerprint ключа
console.log('[INFO] Используемый fingerprint:', userFingerprint);
const challenge = await getChallenge();
const signatureBase64 = await signChallenge(challenge, privateKey);
await loginJWT(userFingerprint, signatureBase64);
})();Ключевое изменение: keyid теперь динамически берётся из расшифрованного ключа через метод getFingerprint(). Это гарантирует, что fingerprint в запросе совпадает с тем, который сервер вычислит при проверке подписи.
Распространённые ошибки и их решения
- Ошибка «Ключ уже расшифрован» при использовании серверного ключа - это нормально, так как серверный ключ Passbolt CE часто хранится без passphrase. Не используйте его для пользовательской авторизации.
- Несовпадение fingerprint после подписи - проверьте, что приватный ключ принадлежит тому же пользователю, чей email указан в
EMAIL. Экспортируйте ключ через веб-интерфейс Passbolt. - Ошибка 400 с сообщением «Учетные данные недействительны» - чаще всего возникает из-за неверного fingerprint в
keyid. ИспользуйтеgetFingerprint()для автоматического получения.
Заключение
Для успешной JWT-авторизации в Passbolt CE через API необходимо использовать приватный ключ того пользователя, от имени которого выполняется запрос, и передавать его fingerprint в теле запроса. Серверный ключ не подходит для этой задачи. Следуя приведённому исправленному коду, вы сможете избежать ошибки «Учетные данные недействительны» и корректно интегрировать Passbolt с вашим приложением.