Skip to content

Commit

Permalink
feat(authtoken): change the authentication token format from JWT to c…
Browse files Browse the repository at this point in the history
…ustom JSON + signature

WE2-585

Signed-off-by: Mart Somermaa <[email protected]>
  • Loading branch information
mrts committed Dec 3, 2021
1 parent 46c0065 commit bfe0567
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 165 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ elseif($ENV{CI_PIPELINE_IID})
else()
set(BUILD_NUMBER 0)
endif()
project(web-eid VERSION 1.0.2.${BUILD_NUMBER})
project(web-eid VERSION 2.0.0.${BUILD_NUMBER})

set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION_TWEAK})
Expand Down
36 changes: 19 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,29 +73,28 @@ members:

### Authenticate

Authentication command creates the OpenID X509 ID Token and signs it with the
authentication key.
Authentication command creates the [Web eID authentication token](https://web-eid.gitlab.io/web-standards-proposals/web-eid-auth-token-format-and-js-api-spec.pdf)
and signs it with the authentication key.

Authentication command requires the nonce, origin URL and Base64-encoded origin
certificate as JSON-encoded command-line arguments:

web-eid -c authenticate '{"nonce": "12345678901234567890123456789012345678901234", "origin": "https://ria.ee", "origin-cert": "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"}'
Authentication command requires the challenge nonce and origin URL as
JSON-encoded command-line arguments:

Origin certificate is optional and may be null.
web-eid -c authenticate '{"challenge-nonce": "12345678901234567890123456789012345678901234", "origin": "https://ria.ee"}'

The result will be written to standard output as a JSON-encoded message that
either contains the OpenID X509 ID Token or an error code. Successful output
either contains the authentication token or an error code. Successful output
example:

{"auth-token": "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlFQXpDQ0EyV2dBd0lCQWdJUU9Xa0JXWE5ESm0xYnlGZDNYc1drdmpBS0JnZ3Foa2pPUFFRREJEQmdNUXN3Q1FZRFZRUUdFd0pGUlRFYk1Ca0dBMVVFQ2d3U1Uwc2dTVVFnVTI5c2RYUnBiMjV6SUVGVE1SY3dGUVlEVlFSaERBNU9WRkpGUlMweE1EYzBOekF4TXpFYk1Ca0dBMVVFQXd3U1ZFVlRWQ0J2WmlCRlUxUkZTVVF5TURFNE1CNFhEVEU0TVRBeE9EQTVOVEEwTjFvWERUSXpNVEF4TnpJeE5UazFPVm93ZnpFTE1Ba0dBMVVFQmhNQ1JVVXhLakFvQmdOVkJBTU1JVXJEbFVWUFVrY3NTa0ZCU3kxTFVrbFRWRXBCVGl3ek9EQXdNVEE0TlRjeE9ERVFNQTRHQTFVRUJBd0hTc09WUlU5U1J6RVdNQlFHQTFVRUtnd05Ta0ZCU3kxTFVrbFRWRXBCVGpFYU1CZ0dBMVVFQlJNUlVFNVBSVVV0TXpnd01ERXdPRFUzTVRnd2RqQVFCZ2NxaGtqT1BRSUJCZ1VyZ1FRQUlnTmlBQVI1azFsWHp2U2VJOU8vMXMxcFp2amhFVzhuSXRKb0cwRUJGeG1MRVk2UzdraTF2RjJRM1RFRHg2ZE56dEkxWHR4OTZjczhyNHpZVHdkaVFvRGc3azNkaVV1UjluVFdHeFFFTU8xRkRvNFk5ZkFtaVBHV1QrK0d1T1ZvWlFZM1h4aWpnZ0hETUlJQnZ6QUpCZ05WSFJNRUFqQUFNQTRHQTFVZER3RUIvd1FFQXdJRGlEQkhCZ05WSFNBRVFEQStNRElHQ3lzR0FRUUJnNUVoQVFJQk1DTXdJUVlJS3dZQkJRVUhBZ0VXRldoMGRIQnpPaTh2ZDNkM0xuTnJMbVZsTDBOUVV6QUlCZ1lFQUk5NkFRSXdId1lEVlIwUkJCZ3dGb0VVTXpnd01ERXdPRFUzTVRoQVpXVnpkR2t1WldVd0hRWURWUjBPQkJZRUZPUXN2VFFKRUJWTU1TbWh5Wlg1YmliWUp1YkFNR0VHQ0NzR0FRVUZCd0VEQkZVd1V6QlJCZ1lFQUk1R0FRVXdSekJGRmo5b2RIUndjem92TDNOckxtVmxMMlZ1TDNKbGNHOXphWFJ2Y25rdlkyOXVaR2wwYVc5dWN5MW1iM0l0ZFhObExXOW1MV05sY25ScFptbGpZWFJsY3k4VEFrVk9NQ0FHQTFVZEpRRUIvd1FXTUJRR0NDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREJEQWZCZ05WSFNNRUdEQVdnQlRBaEprcHhFNmZPd0kwOXBuaENsWUFDQ2srZXpCekJnZ3JCZ0VGQlFjQkFRUm5NR1V3TEFZSUt3WUJCUVVITUFHR0lHaDBkSEE2THk5aGFXRXVaR1Z0Ynk1emF5NWxaUzlsYzNSbGFXUXlNREU0TURVR0NDc0dBUVVGQnpBQ2hpbG9kSFJ3T2k4dll5NXpheTVsWlM5VVpYTjBYMjltWDBWVFZFVkpSREl3TVRndVpHVnlMbU55ZERBS0JnZ3Foa2pPUFFRREJBT0Jpd0F3Z1ljQ1FnSDFVc21NZHRMWnRpNTFGcTJRUjR3VWtBd3BzbmhzQlYySFFxVVhGWUJKN0VYbkxDa2FYamRaS2tIcEFCZk0wUUV4N1VVaGFJNGk1M2ppSjdFMVk3V09BQUpCRFg0ejYxcG5pSEphcEkxYmtNSWlKUS90aTdoYThmZEpTTVNwQWRzNUN5SEl5SGtReldsVnk4NmY5bUE3RXUzb1JPLzFxK2VGVXpEYk5OM1Z2eTdnUVdRPSJdfQ.eyJhdWQiOlsiaHR0cHM6Ly9yaWEuZWUiLCJ1cm46Y2VydDpzaGEtMjU2OjZmMGRmMjQ0ZTRhODU2Yjk0YjNiM2I0NzU4MmEwYTUxYTMyZDY3NGRiYzcxMDcyMTFlZDIzZDRiZWM2ZDljNzIiXSwiZXhwIjoiMTU4Njg3MTE2OSIsImlhdCI6IjE1ODY4NzA4NjkiLCJpc3MiOiJ3ZWItZWlkIGFwcCB2MC45LjAtMS1nZTZlODlmYSIsIm5vbmNlIjoiMTIzNDU2NzgxMjM0NTY3ODEyMzQ1Njc4MTIzNDU2NzgiLCJzdWIiOiJKw5VFT1JHLEpBQUstS1JJU1RKQU4sMzgwMDEwODU3MTgifQ.0Y5CdMiSZ14rOnd7sbp-XeBQ7qrJVd21yTmAbiRnzAXtwqW8ZROg4jL4J7bpQ2fwyUz4-dVwLoVRVnxfJY82b8NXuxXrDb-8MXXmVYrMW0q0kPbEzqFbEnPYHjNnKAN0"}
{
"unverifiedCertificate": "MIIEAzCCA2WgAwIBAgIQHWbVWxCkcYxbzz9nBzGrDzAKBggqhkjOPQQDBDBgMQswCQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRhDA5OVFJFRS0xMDc0NzAxMzEbMBkGA1UEAwwSVEVTVCBvZiBFU1RFSUQyMDE4MB4XDTE4MTAyMzE1MzM1OVoXDTIzMTAyMjIxNTk1OVowfzELMAkGA1UEBhMCRUUxKjAoBgNVBAMMIUrDlUVPUkcsSkFBSy1LUklTVEpBTiwzODAwMTA4NTcxODEQMA4GA1UEBAwHSsOVRU9SRzEWMBQGA1UEKgwNSkFBSy1LUklTVEpBTjEaMBgGA1UEBRMRUE5PRUUtMzgwMDEwODU3MTgwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQ/u+9IncarVpgrACN6aRgUiT9lWC9H7llnxoEXe8xoCI982Md8YuJsVfRdeG5jwVfXe0N6KkHLFRARspst8qnACULkqFNat/Kj+XRwJ2UANeJ3Gl5XBr+tnLNuDf/UiR6jggHDMIIBvzAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIDiDBHBgNVHSAEQDA+MDIGCysGAQQBg5EhAQIBMCMwIQYIKwYBBQUHAgEWFWh0dHBzOi8vd3d3LnNrLmVlL0NQUzAIBgYEAI96AQIwHwYDVR0RBBgwFoEUMzgwMDEwODU3MThAZWVzdGkuZWUwHQYDVR0OBBYEFOTddHnA9rJtbLwhBNyn0xZTQGCMMGEGCCsGAQUFBwEDBFUwUzBRBgYEAI5GAQUwRzBFFj9odHRwczovL3NrLmVlL2VuL3JlcG9zaXRvcnkvY29uZGl0aW9ucy1mb3ItdXNlLW9mLWNlcnRpZmljYXRlcy8TAkVOMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAfBgNVHSMEGDAWgBTAhJkpxE6fOwI09pnhClYACCk+ezBzBggrBgEFBQcBAQRnMGUwLAYIKwYBBQUHMAGGIGh0dHA6Ly9haWEuZGVtby5zay5lZS9lc3RlaWQyMDE4MDUGCCsGAQUFBzAChilodHRwOi8vYy5zay5lZS9UZXN0X29mX0VTVEVJRDIwMTguZGVyLmNydDAKBggqhkjOPQQDBAOBiwAwgYcCQgHYElkX4vn821JR41akI/lpexCnJFUf4GiOMbTfzAxpZma333R8LNrmI4zbzDp03hvMTzH49g1jcbGnaCcbboS8DAJBObenUp++L5VqldHwKAps61nM4V+TiLqD0jILnTzl+pV+LexNL3uGzUfvvDNLHnF9t6ygi8+Bsjsu3iHHyM1haKM=",
"algorithm": "ES384",
"signature": "j8KBTYCXZ8OLuL6eoitRlSmiqw6oIsIJmDm6SttGYvEaJUkBS5kLeCeaokQm5u5viLEJy9iUDONEVlcnLgHIlOZUoEozPNw+AzjI9n7n/D25koYrzmGvMsHX1AKbwqAc",
"format": "web-eid:1.0",
"appVersion": "https://web-eid.eu/web-eid-app/releases/2.0.0+0"
}

The OpenID X509 ID Token is a standard JSON Web Token that can be validated
with e.g. the [JWT.IO online validator](https://jwt.io/). The full
specification of the format is available in the [Web eID system architecture
document](https://github.com/web-eid/web-eid-system-architecture-doc#token-format).
Note that the `aud` field of the token contains an array that contains the
origin URL and, in case the origin certificate is provided, also the
origin certificate SHA-256 fingerprint as second element.
The full specification of the format is available in the [Web eID system
architecture document](https://github.com/web-eid/web-eid-system-architecture-doc#token-format).

### Sign

Expand Down Expand Up @@ -124,7 +123,10 @@ either contains the Base64-encoded signature and the signature algorithm used
(see the description of the `supported-signature-algos` field above in section
_Get certificate_), or an error code. Successful output example:

{"signature-algo": {"hash-algo": "SHA-384", "padding-algo": "NONE", "crypto-algo": "ECC"}, "signature": "oIw20YRlryXgAhGbHEKBCzQetVAE/S2VjqEQ1h+Kc9Scujcl37oOCmAgoHmEkG4Fpmp/z2waGw8ciJ1yXNpgzIaLhtyytFnFmcwR3zp6OKZTqHuEvTEAxZkxC6gLCxJh"}
{
"signature-algo": {"hash-algo": "SHA-384", "padding-algo": "NONE", "crypto-algo": "ECC"},
"signature": "oIw20YRlryXgAhGbHEKBCzQetVAE/S2VjqEQ1h+Kc9Scujcl37oOCmAgoHmEkG4Fpmp/z2waGw8ciJ1yXNpgzIaLhtyytFnFmcwR3zp6OKZTqHuEvTEAxZkxC6gLCxJh"
}

## Changing the user interface language

Expand Down
138 changes: 40 additions & 98 deletions src/controller/command-handlers/authenticate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,55 +38,25 @@ using namespace electronic_id;
namespace
{

const auto JWT_BASE64_OPTIONS = QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals;
// Use common base64-encoding defaults.
constexpr auto BASE64_OPTIONS = QByteArray::Base64Encoding | QByteArray::KeepTrailingEquals;

QByteArray jsonDocToBase64(const QJsonDocument& doc)
QVariantMap createAuthenticationToken(const QString& signatureAlgorithm,
const QByteArray& certificateDer, const QByteArray& signature)
{
return QString(doc.toJson(QJsonDocument::JsonFormat::Compact))
.toUtf8()
.toBase64(JWT_BASE64_OPTIONS);
}

QByteArray createAuthenticationToken(const QSslCertificate& certificate,
const QByteArray& certificateDer,
const QString& signatureAlgorithm, const QString& nonce,
const QString& origin,
const QSslCertificate& originCertificate)
{
const auto tokenHeader = QJsonDocument(QJsonObject {
{"typ", "JWT"},
{"alg", signatureAlgorithm},
{"x5c", QJsonArray({QString(certificateDer.toBase64())})},
});

const QString sub =
certificate.subjectInfo("SN").isEmpty() && certificate.subjectInfo("GN").isEmpty()
? certificate.subjectInfo(QSslCertificate::CommonName).join(',')
: QStringLiteral("%1,%2").arg(certificate.subjectInfo("SN").join(','),
certificate.subjectInfo("GN").join(','));
auto tokenPayload = QJsonObject {
{"iat", QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch())},
{"exp",
QString::number(QDateTime::currentDateTimeUtc().addSecs(5 * 60).toSecsSinceEpoch())},
{"sub", sub},
{"nonce", nonce},
{"iss", QStringLiteral("web-eid app %1").arg(qApp->applicationVersion())},
return QVariantMap {
{"unverifiedCertificate", QString(certificateDer.toBase64(BASE64_OPTIONS))},
{"algorithm", signatureAlgorithm},
{"signature", QString(signature)},
{"format", QStringLiteral("web-eid:1.0")},
{"appVersion",
QStringLiteral("https://web-eid.eu/web-eid-app/releases/%1")
.arg(qApp->applicationVersion())},
};

auto aud = QJsonArray({origin});
if (!originCertificate.isNull()) {
const auto originCertFingerprint =
QString(originCertificate.digest(QCryptographicHash::Sha256).toHex());
// urn:cert:sha-256 as per https://tools.ietf.org/id/draft-seantek-certspec-00.html
aud.append("urn:cert:sha-256:" + originCertFingerprint);
}
tokenPayload[QStringLiteral("aud")] = aud;

return jsonDocToBase64(tokenHeader) + '.' + jsonDocToBase64(QJsonDocument(tokenPayload));
}

QByteArray signToken(const ElectronicID& eid, const QByteArray& token,
const pcsc_cpp::byte_vector& pin)
QByteArray createSignature(const QString& origin, const QString& challengeNonce,
const ElectronicID& eid, const pcsc_cpp::byte_vector& pin)
{
static const auto SIGNATURE_ALGO_TO_HASH =
std::map<JsonWebSignatureAlgorithm, QCryptographicHash::Algorithm> {
Expand All @@ -103,43 +73,44 @@ QByteArray signToken(const ElectronicID& eid, const QByteArray& token,

const auto hashAlgo = SIGNATURE_ALGO_TO_HASH.at(eid.authSignatureAlgorithm());

const auto tokenHashQBytearray = QCryptographicHash::hash(token, hashAlgo);
const auto tokenHash =
pcsc_cpp::byte_vector {tokenHashQBytearray.cbegin(), tokenHashQBytearray.cend()};
// Take the hash of the origin and nonce to ensure field separation.
const auto originHash = QCryptographicHash::hash(origin.toUtf8(), hashAlgo);
const auto challengeNonceHash = QCryptographicHash::hash(challengeNonce.toUtf8(), hashAlgo);

const auto signature = eid.signWithAuthKey(pin, tokenHash);
// The value that is signed is hash(origin)+hash(challenge).
const auto hashToBeSignedQBytearray =
QCryptographicHash::hash(originHash + challengeNonceHash, hashAlgo);
const auto hashToBeSigned =
pcsc_cpp::byte_vector {hashToBeSignedQBytearray.cbegin(), hashToBeSignedQBytearray.cend()};

const auto signatureBase64 =
QByteArray::fromRawData(reinterpret_cast<const char*>(signature.data()),
int(signature.size()))
.toBase64(JWT_BASE64_OPTIONS);
const auto signature = eid.signWithAuthKey(pin, hashToBeSigned);

return token + '.' + signatureBase64;
return QByteArray::fromRawData(reinterpret_cast<const char*>(signature.data()),
int(signature.size()))
.toBase64(BASE64_OPTIONS);
}

} // namespace

Authenticate::Authenticate(const CommandWithArguments& cmd) : CertificateReader(cmd)
{
const auto arguments = cmd.second;
requireArgumentsAndOptionalLang({"nonce", "origin", "origin-cert"}, arguments,
"\"nonce\": \"<challenge nonce>\", "
"\"origin\": \"<origin URL>\", "
"\"origin-cert\": \"<Base64-encoded origin certificate>\"");
requireArgumentsAndOptionalLang({"challenge-nonce", "origin"}, arguments,
"\"challenge-nonce\": \"<challenge nonce>\", "
"\"origin\": \"<origin URL>\"");

nonce = validateAndGetArgument<QString>(QStringLiteral("nonce"), arguments);
challengeNonce = validateAndGetArgument<QString>(QStringLiteral("challenge-nonce"), arguments);
// nonce must contain at least 256 bits of entropy and is usually Base64-encoded, so the
// required byte length is 44, the length of 32 Base64-encoded bytes.
if (nonce.length() < 44) {
if (challengeNonce.length() < 44) {
THROW(CommandHandlerInputDataError,
"Challenge nonce argument 'nonce' must be at least 44 characters long");
"Challenge nonce argument 'challenge-nonce' must be at least 44 characters long");
}
if (nonce.length() > 128) {
if (challengeNonce.length() > 128) {
THROW(CommandHandlerInputDataError,
"Challenge nonce argument 'nonce' cannot be longer than 128 characters");
"Challenge nonce argument 'challenge-nonce' cannot be longer than 128 characters");
}
validateAndStoreOrigin(arguments);
validateAndStoreOriginCertificate(arguments);
}

QVariantMap Authenticate::onConfirm(WebEidUI* window,
Expand All @@ -148,21 +119,19 @@ QVariantMap Authenticate::onConfirm(WebEidUI* window,
const auto signatureAlgorithm =
QString::fromStdString(cardCertAndPin.cardInfo->eid().authSignatureAlgorithm());

const auto token =
createAuthenticationToken(cardCertAndPin.certificate, cardCertAndPin.certificateBytesInDer,
signatureAlgorithm, nonce, origin.url(), originCertificate);

auto pin = getPin(cardCertAndPin.cardInfo->eid().smartcard(), window);

try {
const auto signedToken = signToken(cardCertAndPin.cardInfo->eid(), token, pin);
const auto signature =
createSignature(origin.url(), challengeNonce, cardCertAndPin.cardInfo->eid(), pin);

// Erase the PIN memory.
// TODO: Use a scope guard. Verify that the buffers are actually zeroed
// and no copies remain.
// TODO: Use a scope guard. Verify that the buffers are actually zeroed and no copies
// remain.
std::fill(pin.begin(), pin.end(), '\0');

return {{QStringLiteral("auth-token"), QString(signedToken)}};
return createAuthenticationToken(signatureAlgorithm, cardCertAndPin.certificateBytesInDer,
signature);

} catch (const VerifyPinFailed& failure) {
switch (failure.status()) {
Expand All @@ -185,30 +154,3 @@ void Authenticate::connectSignals(const WebEidUI* window)

connect(this, &Authenticate::verifyPinFailed, window, &WebEidUI::onVerifyPinFailed);
}

void Authenticate::validateAndStoreOriginCertificate(const QVariantMap& args)
{
originCertificate = parseAndValidateCertificate(QStringLiteral("origin-cert"), args, true);
if (originCertificate.isNull()) {
return;
}

const auto certHostnames = originCertificate.subjectInfo(QSslCertificate::CommonName);
if (certHostnames.size() != 1) {
// TODO: add support for multi-domain certificates
THROW(CommandHandlerInputDataError,
"Origin certificate does not contain exactly 1 host name (it contains "
+ std::to_string(certHostnames.size()) + ")");
}
if (origin.host() != certHostnames[0]
// Certificate hostname may be a wildcard, e.g. *.ria.ee, use QDir::match() for glob
// matching. Origin hostname may be either e.g. www.ria.ee that matches *.ria.ee directly,
&& !QDir::match(certHostnames[0], origin.host())
// or ria.ee that needs an extra dot prefix to match.
&& !QDir::match(certHostnames[0], '.' + origin.host())) {
THROW(CommandHandlerInputDataError,
"Origin host name '" + origin.host().toStdString()
+ "' does not match origin certificate host name '"
+ certHostnames[0].toStdString() + "'");
}
}
5 changes: 1 addition & 4 deletions src/controller/command-handlers/authenticate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,5 @@ class Authenticate : public CertificateReader
const qint8 retriesLeft);

private:
void validateAndStoreOriginCertificate(const QVariantMap& args);

QString nonce;
QSslCertificate originCertificate;
QString challengeNonce;
};
2 changes: 1 addition & 1 deletion src/controller/command-handlers/signauthutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ void requireArgumentsAndOptionalLang(QStringList argNames, const QVariantMap& ar
// QMap::keys() also returns a list containing all the keys in the map in ascending order.
if (argCopy.keys() != argNames) {
THROW(CommandHandlerInputDataError,
"Argument must be '{" + argDescriptions
"Arguments must be '{" + argDescriptions
+ ", \"lang\": \"<OPTIONAL user interface language>\"}'");
}
}
Expand Down
Loading

0 comments on commit bfe0567

Please sign in to comment.