diff --git a/README.md b/README.md index 8dd2a03..c6442b5 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,14 @@ open standard [RFC 7519](https://tools.ietf.org/html/rfc7519). Currently this implementation **only supports** the following algorithms: -Alg | Parameter Value Algorithm ------ | ------------------------------------ -HS256 | HMAC using SHA-256 hash algorithm -HS384 | HMAC using SHA-384 hash algorithm -HS512 | HMAC using SHA-512 hash algorithm +Alg | Parameter Value Algorithm | Using library +----- | --------------------------------- | -------------- +HS256 | HMAC using SHA-256 hash algorithm | Qt +HS384 | HMAC using SHA-384 hash algorithm | Qt +HS512 | HMAC using SHA-512 hash algorithm | Qt +RS256 | RSA using SHA-256 hash algorithm | QCA (optional) +RS384 | RSA using SHA-384 hash algorithm | QCA (optional) +RS512 | RSA using SHA-512 hash algorithm | QCA (optional) ### Include @@ -30,10 +33,6 @@ The repository of this project includes examples that demonstrate the use of thi * ```./examples/jwtverifier/``` : Example that shows how to validate a JWT with a given *secret*. -### Limitations - -Currently, `QJsonWebToken` validator, can **only** validate tokens created by `QJsonWebToken` itself. This limitation is due to the usage of Qt's [QJsonDocument API](http://doc.qt.io/qt-5/qjsondocument.html), see [this issue for further explanation](https://github.com/juangburgos/QJsonWebToken/issues/3#issuecomment-333056575). - ### License MIT diff --git a/examples/jwtcreator/dialog.cpp b/examples/jwtcreator/dialog.cpp index caae2cf..0902c81 100644 --- a/examples/jwtcreator/dialog.cpp +++ b/examples/jwtcreator/dialog.cpp @@ -11,31 +11,36 @@ #include Dialog::Dialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::Dialog) + QDialog(parent), + ui(new Ui::Dialog), + hsKey(QJsonWebKey::fromOctet("mydirtysecret")), + rsKey(nullptr) { - ui->setupUi(this); + ui->setupUi(this); // set some tooltips ui->pushRemoveClaim->setToolTip("To remove a claim you just need to define the Claim Type<\b>"); ui->pushRemoveClaim->setToolTipDuration(3500); // set default secret - ui->lineSecret->setText("mydirtysecret"); - m_jwtObj.setSecret("mydirtysecret"); + ui->plainTextEditKey->setPlainText(QString::fromUtf8(hsKey->toJson())); + m_jwtObj.setKey(hsKey); // set a default payload m_jwtObj.appendClaim("iss", "juangburgos"); - m_jwtObj.appendClaim("iat", QString::number(QDateTime::currentDateTime().toTime_t())); - m_jwtObj.appendClaim("exp", QString::number(QDateTime::currentDateTime().addDays(7).toTime_t())); + m_jwtObj.appendClaim("iat", static_cast(QDateTime::currentDateTime().toTime_t())); + m_jwtObj.appendClaim("exp", static_cast(QDateTime::currentDateTime().addDays(7).toTime_t())); m_jwtObj.appendClaim("aud", "everybody"); m_jwtObj.appendClaim("sub", "hey there"); // set current value to views ui->plainTextClaims->setPlainText(m_jwtObj.getPayloadQStr()); // setup combobox (exec at the end because it calls slot) ui->comboAlgorithm->addItems(QJsonWebToken::supportedAlgorithms()); +#ifdef USE_QCA + rsKey = QJsonWebKey::generateRSAPrivateKey(2048); +#endif // USE_QCA } Dialog::~Dialog() { - delete ui; + delete ui; } void Dialog::on_pushAddClaim_clicked() @@ -79,28 +84,88 @@ void Dialog::on_pushRemoveClaim_clicked() void Dialog::on_comboAlgorithm_currentIndexChanged(const QString &arg1) { + if (!arg1.startsWith(m_jwtObj.getAlgorithmStr().left(2))) + { + // change algorithm + if (arg1.startsWith("HS")) + { + m_jwtObj.setKey(hsKey); + ui->plainTextEditKey->blockSignals(true); + ui->plainTextEditKey->setPlainText(QString::fromUtf8(hsKey->toJson())); + ui->plainTextEditKey->blockSignals(false); + } +#ifdef USE_QCA + else if (arg1.startsWith("RS")) + { + if (rsKey.isNull()) + { + rsKey = QJsonWebKey::generateRSAPrivateKey(2048); + } + m_jwtObj.setKey(rsKey); + ui->plainTextEditKey->blockSignals(true); + ui->plainTextEditKey->setPlainText(!rsKey.isNull() ? QString::fromUtf8(rsKey->toJson()) : QString()); + ui->plainTextEditKey->blockSignals(false); + } +#endif // USE_QCA + } // set new secret m_jwtObj.setAlgorithmStr(arg1); // show new jwt ui->plainTextSignedJwt->setPlainText(m_jwtObj.getToken()); } -void Dialog::on_lineSecret_textChanged(const QString &arg1) +void Dialog::on_plainTextEditKey_textChanged() { - // set new secret - m_jwtObj.setSecret(ui->lineSecret->text()); - // show new jwt - ui->plainTextSignedJwt->setPlainText(m_jwtObj.getToken()); + QString text = ui->plainTextEditKey->toPlainText(); + if (ui->comboAlgorithm->currentText().startsWith("HS")) + { + hsKey = QJsonWebKey::fromJsonWebKey(text.toUtf8()); + // set new secret + m_jwtObj.setKey(hsKey); + } +#ifdef USE_QCA + else if (ui->comboAlgorithm->currentText().startsWith("RS")) + { + rsKey = QJsonWebKey::fromJsonWebKey(text.toUtf8()); + // set new secret + m_jwtObj.setKey(rsKey); + } +#endif // USE_QCA + // show new jwt + ui->plainTextSignedJwt->setPlainText(m_jwtObj.getToken()); } void Dialog::on_pushRandom_clicked() { - // set random secret - m_jwtObj.setRandomSecret(); - // set random secret in lineedit - ui->lineSecret->blockSignals(true); - ui->lineSecret->setText(m_jwtObj.getSecret()); - ui->lineSecret->blockSignals(false); - // show new jwt - ui->plainTextSignedJwt->setPlainText(m_jwtObj.getToken()); + if (ui->comboAlgorithm->currentText().startsWith("HS")) + { + // set random secret + int randLength = 10; + QByteArray randAlphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + QByteArray secret; + secret.resize(randLength); + for (int i = 0; i < randLength; ++i) + { + secret[i] = randAlphanum.at(rand() % (randAlphanum.length() - 1)); + } + hsKey = QJsonWebKey::fromOctet(secret); + m_jwtObj.setKey(hsKey); + // set random secret in lineedit + ui->plainTextEditKey->blockSignals(true); + ui->plainTextEditKey->setPlainText(QString::fromUtf8(hsKey->toJson())); + ui->plainTextEditKey->blockSignals(false); + } +#ifdef USE_QCA + else if (ui->comboAlgorithm->currentText().startsWith("RS")) + { + rsKey = QJsonWebKey::generateRSAPrivateKey(2048); + m_jwtObj.setKey(rsKey); + // set random secret in lineedit + ui->plainTextEditKey->blockSignals(true); + ui->plainTextEditKey->setPlainText(!rsKey.isNull() ? QString::fromUtf8(rsKey->toJson()) : QString()); + ui->plainTextEditKey->blockSignals(false); + } +#endif // USE_QCA + // show new jwt + ui->plainTextSignedJwt->setPlainText(m_jwtObj.getToken()); } diff --git a/examples/jwtcreator/dialog.h b/examples/jwtcreator/dialog.h index 2e19c91..00b6e78 100644 --- a/examples/jwtcreator/dialog.h +++ b/examples/jwtcreator/dialog.h @@ -32,12 +32,14 @@ private slots: void on_comboAlgorithm_currentIndexChanged(const QString &arg1); - void on_lineSecret_textChanged(const QString &arg1); + void on_plainTextEditKey_textChanged(); void on_pushRandom_clicked(); private: Ui::Dialog *ui; + QSharedPointer hsKey; + QSharedPointer rsKey; }; #endif // DIALOG_H diff --git a/examples/jwtcreator/dialog.ui b/examples/jwtcreator/dialog.ui index 1631e35..aa468a3 100644 --- a/examples/jwtcreator/dialog.ui +++ b/examples/jwtcreator/dialog.ui @@ -7,7 +7,7 @@ 0 0 564 - 604 + 694 @@ -99,54 +99,62 @@ JWT Signer - - - - Secret : - - - - - - - - - - Random - - - - - - - Qt::Vertical - - - - - - - Algorithm : - - - - - - - - 100 - 0 - - - - - + true + + + + JWK + + + + + + + + + Algorithm : + + + + + + + + 100 + 0 + + + + + + + + Random + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + diff --git a/examples/jwtcreator/main.cpp b/examples/jwtcreator/main.cpp index d52642e..56b8cbb 100644 --- a/examples/jwtcreator/main.cpp +++ b/examples/jwtcreator/main.cpp @@ -6,10 +6,27 @@ #include "dialog.h" #include +#include +#include int main(int argc, char *argv[]) { QApplication a(argc, argv); + +#ifdef USE_QCA + QDir dir(QApplication::applicationDirPath()); + dir.cd("plugins"); + QApplication::addLibraryPath(dir.absolutePath()); + + QCA::Initializer init; + Q_UNUSED(init); + + if (!QCA::isSupported("pkey") || !QCA::PKey::supportedIOTypes().contains(QCA::PKey::RSA)) + { + qDebug() << "RSA not supported."; + } +#endif + Dialog w; w.show(); diff --git a/examples/jwtverifier/dialog.cpp b/examples/jwtverifier/dialog.cpp index 7713152..b777195 100644 --- a/examples/jwtverifier/dialog.cpp +++ b/examples/jwtverifier/dialog.cpp @@ -8,17 +8,17 @@ #include "ui_dialog.h" Dialog::Dialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::Dialog) + QDialog(parent), + ui(new Ui::Dialog) { - ui->setupUi(this); + ui->setupUi(this); // setup initial button color ui->pushStatus->setStyleSheet("background-color: #ff8080; color: black; font: bold;"); } Dialog::~Dialog() { - delete ui; + delete ui; } void Dialog::on_plainTextEncoded_textChanged() @@ -34,16 +34,16 @@ void Dialog::on_plainTextEncoded_textChanged() ui->plainTextPayload->setPlainText(QObject::trUtf8("ERROR : token must have the format xxxx.yyyyy.zzzzz")); return; } - QString strSecret = ui->lineEditSecret->text(); - if (strSecret.isEmpty()) + QString key = ui->plainTextEditKey->toPlainText(); + if (key.isEmpty()) { // show error - ui->plainTextHeader->setPlainText(QObject::trUtf8("ERROR : secret must be non-empty")); - ui->plainTextPayload->setPlainText(QObject::trUtf8("ERROR : secret must be non-empty")); + ui->plainTextHeader->setPlainText(QObject::trUtf8("ERROR : key must be non-empty")); + ui->plainTextPayload->setPlainText(QObject::trUtf8("ERROR : key must be non-empty")); return; } // set token and secret - QJsonWebToken token = QJsonWebToken::fromTokenAndSecret(strToken, strSecret); + QJsonWebToken token = QJsonWebToken::fromTokenAndKey(strToken, QJsonWebKey::fromJsonWebKey(key.toUtf8())); // get decoded header and payload QString strHeader = token.getHeaderQStr(); QString strPayload = token.getPayloadQStr(); @@ -63,7 +63,12 @@ void Dialog::on_plainTextEncoded_textChanged() } -void Dialog::on_lineEditSecret_textChanged(const QString &arg1) +void Dialog::on_checkBoxIsOctet_stateChanged() +{ + on_plainTextEncoded_textChanged(); +} + +void Dialog::on_plainTextEditKey_textChanged() { on_plainTextEncoded_textChanged(); } diff --git a/examples/jwtverifier/dialog.h b/examples/jwtverifier/dialog.h index c9ce04e..16425b2 100644 --- a/examples/jwtverifier/dialog.h +++ b/examples/jwtverifier/dialog.h @@ -25,8 +25,9 @@ class Dialog : public QDialog private slots: void on_plainTextEncoded_textChanged(); + void on_checkBoxIsOctet_stateChanged(); - void on_lineEditSecret_textChanged(const QString &arg1); + void on_plainTextEditKey_textChanged(); private: Ui::Dialog *ui; diff --git a/examples/jwtverifier/dialog.ui b/examples/jwtverifier/dialog.ui index 04bb24c..0c6a182 100644 --- a/examples/jwtverifier/dialog.ui +++ b/examples/jwtverifier/dialog.ui @@ -108,13 +108,13 @@ - Secret : + JWK : + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - @@ -135,6 +135,9 @@ + + + diff --git a/examples/jwtverifier/main.cpp b/examples/jwtverifier/main.cpp index d52642e..0e9bf87 100644 --- a/examples/jwtverifier/main.cpp +++ b/examples/jwtverifier/main.cpp @@ -6,10 +6,27 @@ #include "dialog.h" #include +#include +#include int main(int argc, char *argv[]) { QApplication a(argc, argv); + +#ifdef USE_QCA + QDir dir(QApplication::applicationDirPath()); + dir.cd("plugins"); + QApplication::addLibraryPath(dir.absolutePath()); + + QCA::Initializer init; + Q_UNUSED(init); + + if (!QCA::isSupported("pkey") || !QCA::PKey::supportedIOTypes().contains(QCA::PKey::RSA)) + { + qDebug() << "RSA not supported."; + } +#endif // USE_QCA + Dialog w; w.show(); diff --git a/src/qjsonwebtoken.cpp b/src/qjsonwebtoken.cpp index 7dd0bcc..fefa309 100644 --- a/src/qjsonwebtoken.cpp +++ b/src/qjsonwebtoken.cpp @@ -8,175 +8,493 @@ #include -QJsonWebToken::QJsonWebToken() +// constants +static const QString JWT_ALG_HS256 = "HS256"; // HMAC using SHA-256 hash algorithm +static const QString JWT_ALG_HS384 = "HS384"; // HMAC using SHA-384 hash algorithm +static const QString JWT_ALG_HS512 = "HS512"; // HMAC using SHA-512 hash algorithm +static const QString JWT_ALG_RS256 = "RS256"; // RSA using SHA-256 hash algorithm +static const QString JWT_ALG_RS384 = "RS384"; // RSA using SHA-384 hash algorithm +static const QString JWT_ALG_RS512 = "RS512"; // RSA using SHA-512 hash algorithm +static const QString JWT_ALG_ES256 = "ES256"; // ECDSA using P-256 curve and SHA-256 hash algorithm +static const QString JWT_ALG_ES384 = "ES384"; // ECDSA using P-384 curve and SHA-384 hash algorithm +static const QString JWT_ALG_ES512 = "ES512"; // ECDSA using P-521 curve and SHA-512 hash algorithm +static const QString JWK_ALG_OCT = "oct"; +static const QString JWK_ALG_RSA = "RSA"; +static const QString JWK_ALG_EC = "EC"; + + +// RFC7515 Section 2 Terminology +// Base64url Encoding: +// Base64 encoding using the URL- and filename-safe character set +// defined in Section 5 of RFC 4648 [RFC4648], with all trailing '=' +// characters omitted (as permitted by Section 3.2) and without the +// inclusion of any line breaks, whitespace, or other additional +// characters. Note that the base64url encoding of the empty octet +// sequence is the empty string. (See Appendix C for notes on +// implementing base64url encoding without padding.) +QByteArray toBase64Url(const QByteArray &data) { - // create the header with default algorithm - setAlgorithmStr("HS256"); - m_jdocPayload = QJsonDocument::fromJson("{}"); - // default for random generation - m_intRandLength = 10; - m_strRandAlphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + return data.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); } -QJsonWebToken::QJsonWebToken(const QJsonWebToken &other) +QByteArray fromBase64Url(const QByteArray &base64) { - this->m_jdocHeader = other.m_jdocHeader; - this->m_jdocPayload = other.m_jdocPayload; - this->m_byteSignature = other.m_byteSignature; - this->m_strSecret = other.m_strSecret; - this->m_strAlgorithm = other.m_strAlgorithm; + return QByteArray::fromBase64(base64, QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); } -QJsonDocument QJsonWebToken::getHeaderJDoc() +QByteArray fromBase64Url(const QString &base64) { - return m_jdocHeader; + return fromBase64Url(base64.toUtf8()); } -QString QJsonWebToken::getHeaderQStr(QJsonDocument::JsonFormat format /*= QJsonDocument::JsonFormat::Indented*/) + +class QJsonWebKeyOctet : public QJsonWebKey, public QEnableSharedFromThis { - return m_jdocHeader.toJson(format); -} +public: + QJsonWebKeyOctet(const QByteArray &secret) : + secret(secret) + {} + virtual ~QJsonWebKeyOctet() + {} + + KeyType type() const override + { return Octet; } + + bool isPrivate() const override + { return true; } + + QByteArray sign(const QString &algorithm, const QByteArray &data) const override + { + // calculate + if (algorithm.compare(JWT_ALG_HS256, Qt::CaseInsensitive) == 0) // HMAC using SHA-256 hash algorithm + { + return QMessageAuthenticationCode::hash(data, secret, QCryptographicHash::Sha256); + } + else if (algorithm.compare(JWT_ALG_HS384, Qt::CaseInsensitive) == 0) // HMAC using SHA-384 hash algorithm + { + return QMessageAuthenticationCode::hash(data, secret, QCryptographicHash::Sha384); + } + else if (algorithm.compare(JWT_ALG_HS512, Qt::CaseInsensitive) == 0) // HMAC using SHA-512 hash algorithm + { + return QMessageAuthenticationCode::hash(data, secret, QCryptographicHash::Sha512); + } + else + { + return QByteArray(); + } + } + + bool verify(const QString &algorithm, const QByteArray &signature, const QByteArray &data) const override + { + return signature == sign(algorithm, data); + } + + QSharedPointer toPublic() override + { return sharedFromThis(); } + + QByteArray toJson() const override + { + QJsonObject obj; + obj["kty"] = JWK_ALG_OCT; + obj["k"] = QString::fromUtf8(toBase64Url(secret)); + return QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Compact); + } + + QStringList supportedAlgorithms() const override + { + return QStringList() << JWT_ALG_HS256 << JWT_ALG_HS384 << JWT_ALG_HS512; + } + +private: + QByteArray secret; + +}; -bool QJsonWebToken::setHeaderJDoc(QJsonDocument jdocHeader) +#ifdef USE_QCA +class QJsonWebKeyRSAPublic : public QJsonWebKey, public QEnableSharedFromThis { - if (jdocHeader.isEmpty() || jdocHeader.isNull() || !jdocHeader.isObject()) +public: + QJsonWebKeyRSAPublic(const QCA::PublicKey &key) : + key(key) + {} + virtual ~QJsonWebKeyRSAPublic() + {} + + KeyType type() const override + { return RSA; } + + bool isPrivate() const override + { return false; } + + QByteArray sign(const QString &, const QByteArray &) const override { - return false; + // public key cannot sign + return QByteArray(); } - // check if supported algorithm - QString strAlgorithm = jdocHeader.object().value("alg").toString(""); - if (!isAlgorithmSupported(strAlgorithm)) + bool verify(const QString &algorithm, const QByteArray &signature, const QByteArray &data) const override { - return false; + QCA::MemoryRegion mdata(data); + QCA::PublicKey key = this->key; + if (algorithm.compare(JWT_ALG_RS256, Qt::CaseInsensitive) == 0) // RSA using SHA-256 hash algorithm + { + return key.verifyMessage(mdata, signature, QCA::EMSA3_SHA256); + } + else if (algorithm.compare(JWT_ALG_RS384, Qt::CaseInsensitive) == 0) // RSA using SHA-384 hash algorithm + { + return key.verifyMessage(mdata, signature, QCA::EMSA3_SHA384); + } + else if (algorithm.compare(JWT_ALG_RS512, Qt::CaseInsensitive) == 0) // RSA using SHA-512 hash algorithm + { + return key.verifyMessage(mdata, signature, QCA::EMSA3_SHA512); + } + else + { + return false; + } } - m_jdocHeader = jdocHeader; + QSharedPointer toPublic() override + { return sharedFromThis(); } - // set also new algorithm - m_strAlgorithm = strAlgorithm; + QByteArray toJson() const override + { + const QCA::RSAPublicKey pub = key.toRSA(); + if (pub.isNull()) + { + return QByteArray(); + } + QJsonObject obj; + obj["kty"] = JWK_ALG_RSA; + obj["n"] = QString::fromUtf8(toBase64Url(pub.n().toArray().toByteArray())); + obj["e"] = QString::fromUtf8(toBase64Url(pub.e().toArray().toByteArray())); + return QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Compact); + } - return true; -} + QStringList supportedAlgorithms() const override + { + return QStringList() << JWT_ALG_RS256 << JWT_ALG_RS384 << JWT_ALG_RS512; + } -bool QJsonWebToken::setHeaderQStr(QString strHeader) +private: + QCA::PublicKey key; + +}; + +class QJsonWebKeyRSAPrivate : public QJsonWebKey +{ +public: + QJsonWebKeyRSAPrivate(const QCA::PrivateKey &key) : + key(key), + pub(key) + {} + virtual ~QJsonWebKeyRSAPrivate() + {} + + KeyType type() const override + { return RSA; } + + bool isPrivate() const override + { return true; } + + QByteArray sign(const QString &algorithm, const QByteArray &data) const override + { + if (key.isNull()) + { + return QByteArray(); + } + // calculate + QCA::MemoryRegion mdata(data); + QCA::PrivateKey key = this->key; + if (algorithm.compare(JWT_ALG_RS256, Qt::CaseInsensitive) == 0) // RSA using SHA-256 hash algorithm + { + return key.signMessage(mdata, QCA::EMSA3_SHA256); + } + else if (algorithm.compare(JWT_ALG_RS384, Qt::CaseInsensitive) == 0) // RSA using SHA-384 hash algorithm + { + return key.signMessage(mdata, QCA::EMSA3_SHA384); + } + else if (algorithm.compare(JWT_ALG_RS512, Qt::CaseInsensitive) == 0) // RSA using SHA-512 hash algorithm + { + return key.signMessage(mdata, QCA::EMSA3_SHA512); + } + else + { + return QByteArray(); + } + } + + bool verify(const QString &algorithm, const QByteArray &signature, const QByteArray &data) const override + { + QCA::MemoryRegion mdata(data); + QCA::PublicKey pub = this->pub; + if (algorithm.compare(JWT_ALG_RS256, Qt::CaseInsensitive) == 0) // RSA using SHA-256 hash algorithm + { + return pub.verifyMessage(mdata, signature, QCA::EMSA3_SHA256); + } + else if (algorithm.compare(JWT_ALG_RS384, Qt::CaseInsensitive) == 0) // RSA using SHA-384 hash algorithm + { + return pub.verifyMessage(mdata, signature, QCA::EMSA3_SHA384); + } + else if (algorithm.compare(JWT_ALG_RS512, Qt::CaseInsensitive) == 0) // RSA using SHA-512 hash algorithm + { + return pub.verifyMessage(mdata, signature, QCA::EMSA3_SHA512); + } + else + { + return false; + } + } + + QSharedPointer toPublic() override + { return QSharedPointer::create(pub); } + + QByteArray toJson() const override + { + const QCA::RSAPrivateKey pri = key.toRSA(); + if (pri.isNull()) + { + return QByteArray(); + } + QJsonObject obj; + obj["kty"] = JWK_ALG_RSA; + obj["n"] = QString::fromUtf8(toBase64Url(pri.n().toArray().toByteArray())); + obj["e"] = QString::fromUtf8(toBase64Url(pri.e().toArray().toByteArray())); + obj["p"] = QString::fromUtf8(toBase64Url(pri.p().toArray().toByteArray())); + obj["q"] = QString::fromUtf8(toBase64Url(pri.q().toArray().toByteArray())); + obj["d"] = QString::fromUtf8(toBase64Url(pri.d().toArray().toByteArray())); + return QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Compact); + } + + QStringList supportedAlgorithms() const override + { + return QStringList() << JWT_ALG_RS256 << JWT_ALG_RS384 << JWT_ALG_RS512; + } + +private: + QCA::PrivateKey key; + QCA::PublicKey pub; + +}; +#endif // USE_QCA + +QSharedPointer QJsonWebKey::fromJsonWebKey(const QByteArray &jwk) { QJsonParseError error; - QJsonDocument tmpHeader = QJsonDocument::fromJson(strHeader.toUtf8(), &error); + QJsonDocument doc = QJsonDocument::fromJson(jwk, &error); + if (error.error != QJsonParseError::NoError || doc.isNull() || !doc.isObject() || !doc.object().contains("kty")) + { + return nullptr; + } + QJsonObject obj = doc.object(); + QString type = obj.value("kty").toString(""); + if (type == JWK_ALG_OCT && obj.contains("k")) + { + return fromOctet(fromBase64Url(obj.value("k").toString("").toUtf8())); + } + else if (type == JWK_ALG_RSA && obj.contains("n") && obj.contains("e") && obj.contains("d") && obj.contains("p") && obj.contains("q")) + { + // private RSA +#ifdef USE_QCA + QCA::BigInteger n = QCA::SecureArray(fromBase64Url(obj.value("n").toString(""))); + QCA::BigInteger e = QCA::SecureArray(fromBase64Url(obj.value("e").toString(""))); + QCA::BigInteger d = QCA::SecureArray(fromBase64Url(obj.value("d").toString(""))); + QCA::BigInteger p = QCA::SecureArray(fromBase64Url(obj.value("p").toString(""))); + QCA::BigInteger q = QCA::SecureArray(fromBase64Url(obj.value("q").toString(""))); + return QSharedPointer::create(QCA::RSAPrivateKey(n, e, p, q, d)); +#else + return nullptr; +#endif + } + else if (type == JWK_ALG_RSA && obj.contains("n") && obj.contains("e")) + { + // public RSA +#ifdef USE_QCA + QCA::BigInteger n = QCA::SecureArray(fromBase64Url(obj.value("n").toString(""))); + QCA::BigInteger e = QCA::SecureArray(fromBase64Url(obj.value("e").toString(""))); + return QSharedPointer::create(QCA::RSAPublicKey(n, e)); +#else + return nullptr; +#endif + } + else + { + // unknown key + return nullptr; + } +} - // validate and set header - if (error.error != QJsonParseError::NoError || !setHeaderJDoc(tmpHeader)) +QSharedPointer QJsonWebKey::fromOctet(const QByteArray &data) +{ + return QSharedPointer::create(data); +} + +#ifdef USE_QCA +QSharedPointer QJsonWebKey::generateRSAPrivateKey(int bits) +{ + QCA::PrivateKey key = QCA::KeyGenerator().createRSA(bits); + if (key.isNull()) { - return false; + return nullptr; } + return QSharedPointer::create(key); +} +#endif // USE_QCA + + +QJsonWebToken::QJsonWebToken() : + m_byteHeader(), + m_bytePayload(), + m_jdocHeader(), + m_jdocPayload(QJsonDocument::fromJson("{}")), + m_byteSignature(), + m_strAlgorithm(), + m_byteAllData(), + m_jwk(nullptr) +{ + // create the header with default algorithm + setAlgorithmStr("HS256"); +} - return true; +QJsonDocument QJsonWebToken::getHeaderJDoc() +{ + return m_jdocHeader; } -QJsonDocument QJsonWebToken::getPayloadJDoc() +QString QJsonWebToken::getHeaderQStr(bool pretty) const { - return m_jdocPayload; + if (pretty) + { + return m_jdocHeader.toJson(QJsonDocument::JsonFormat::Indented); + } + else + { + return QString::fromUtf8(m_byteHeader); + } } -QString QJsonWebToken::getPayloadQStr(QJsonDocument::JsonFormat format /*= QJsonDocument::JsonFormat::Indented*/) +QByteArray QJsonWebToken::getHeaderJson() const { - return m_jdocPayload.toJson(format); + return m_byteHeader; } -bool QJsonWebToken::setPayloadJDoc(QJsonDocument jdocPayload) +bool QJsonWebToken::setHeaderJDoc(const QJsonDocument &jdocHeader) { - if (jdocPayload.isEmpty() || jdocPayload.isNull() || !jdocPayload.isObject()) + if (!isValidHeader(jdocHeader)) { return false; } - m_jdocPayload = jdocPayload; + updateHeader(jdocHeader); return true; } -bool QJsonWebToken::setPayloadQStr(QString strPayload) +bool QJsonWebToken::setHeaderQStr(QString strHeader) { - QJsonParseError error; - QJsonDocument tmpPayload = QJsonDocument::fromJson(strPayload.toUtf8(), &error); + return setHeaderJson(strHeader.toUtf8()); +} - // validate and set payload - if (error.error != QJsonParseError::NoError || !setPayloadJDoc(tmpPayload)) +bool QJsonWebToken::setHeaderJson(const QByteArray &jsonHeader) +{ + if (!isValidHeader(jsonHeader)) { return false; } + updateHeader(jsonHeader); + return true; } -QByteArray QJsonWebToken::getSignature() +QJsonDocument QJsonWebToken::getPayloadJDoc() { - // recalculate - // get header in compact mode and base64 encoded - QByteArray byteHeaderBase64 = getHeaderQStr(QJsonDocument::JsonFormat::Compact).toUtf8().toBase64(); - // get payload in compact mode and base64 encoded - QByteArray bytePayloadBase64 = getPayloadQStr(QJsonDocument::JsonFormat::Compact).toUtf8().toBase64(); - // calculate signature based on chosen algorithm and secret - m_byteAllData = byteHeaderBase64 + "." + bytePayloadBase64; - if (m_strAlgorithm.compare("HS256", Qt::CaseInsensitive) == 0) // HMAC using SHA-256 hash algorithm + return m_jdocPayload; +} + +QString QJsonWebToken::getPayloadQStr(bool pretty) const +{ + if (pretty) { - m_byteSignature = QMessageAuthenticationCode::hash(m_byteAllData, m_strSecret.toUtf8(), QCryptographicHash::Sha256); + return m_jdocPayload.toJson(QJsonDocument::JsonFormat::Indented); } - else if (m_strAlgorithm.compare("HS384", Qt::CaseInsensitive) == 0) // HMAC using SHA-384 hash algorithm + else { - m_byteSignature = QMessageAuthenticationCode::hash(m_byteAllData, m_strSecret.toUtf8(), QCryptographicHash::Sha384); + return QString::fromUtf8(m_bytePayload); } - else if (m_strAlgorithm.compare("HS512", Qt::CaseInsensitive) == 0) // HMAC using SHA-512 hash algorithm +} + +QByteArray QJsonWebToken::getPayloadJson() const +{ + return m_bytePayload; +} + +bool QJsonWebToken::setPayloadJDoc(const QJsonDocument &jdocPayload) +{ + if (!isValidPayload(jdocPayload)) { - m_byteSignature = QMessageAuthenticationCode::hash(m_byteAllData, m_strSecret.toUtf8(), QCryptographicHash::Sha512); + return false; } - // TODO : support other algorithms - else + + updatePayload(jdocPayload); + + return true; +} + +bool QJsonWebToken::setPayloadQStr(const QString &strPayload) +{ + return setPayloadJson(strPayload.toUtf8()); +} + +bool QJsonWebToken::setPayloadJson(const QByteArray &jsonPayload) +{ + if (!isValidPayload(jsonPayload)) { - m_byteSignature = QByteArray(); + return false; } - // return recalculated + + updatePayload(jsonPayload); + + return true; +} + +QByteArray QJsonWebToken::getSignature() const +{ return m_byteSignature; } -QByteArray QJsonWebToken::getSignatureBase64() +QByteArray QJsonWebToken::getSignatureBase64() const { - // note we return through getSignature() to force recalculation - return getSignature().toBase64(); + return toBase64Url(getSignature()); } -QString QJsonWebToken::getSecret() +QSharedPointer QJsonWebToken::getKey() const { - return m_strSecret; + return m_jwk; } -bool QJsonWebToken::setSecret(QString strSecret) +bool QJsonWebToken::setKey(const QSharedPointer &key) { - if (strSecret.isEmpty() || strSecret.isNull()) + if (key.isNull()) { return false; } - m_strSecret = strSecret; - - return true; -} + m_jwk = key; + if (m_jwk->isPrivate()) + { + updateSignature(); + } -void QJsonWebToken::setRandomSecret() -{ - m_strSecret.resize(m_intRandLength); - for (int i = 0; i < m_intRandLength; ++i) - { - m_strSecret[i] = m_strRandAlphanum.at(rand() % (m_strRandAlphanum.length() - 1)); - } + return true; } -QString QJsonWebToken::getAlgorithmStr() +QString QJsonWebToken::getAlgorithmStr() const { return m_strAlgorithm; } -bool QJsonWebToken::setAlgorithmStr(QString strAlgorithm) +bool QJsonWebToken::setAlgorithmStr(const QString &strAlgorithm) { // check if supported algorithm if (!isAlgorithmSupported(strAlgorithm)) @@ -186,14 +504,14 @@ bool QJsonWebToken::setAlgorithmStr(QString strAlgorithm) // set algorithm m_strAlgorithm = strAlgorithm; // modify header - m_jdocHeader = QJsonDocument::fromJson(QObject::trUtf8("{\"typ\": \"JWT\", \"alg\" : \"").toUtf8() - + m_strAlgorithm.toUtf8() - + QObject::trUtf8("\"}").toUtf8()); + setHeaderJDoc(QJsonDocument::fromJson(QObject::trUtf8("{\"typ\": \"JWT\", \"alg\" : \"").toUtf8() + + m_strAlgorithm.toUtf8() + + QObject::trUtf8("\"}").toUtf8())); return true; } -QString QJsonWebToken::getToken() +QString QJsonWebToken::getToken() const { // important to execute first to update m_byteAllData which contains header + "." + payload in base64 QByteArray byteSignatureBase64 = getSignatureBase64(); @@ -201,7 +519,7 @@ QString QJsonWebToken::getToken() return m_byteAllData + "." + byteSignatureBase64; } -bool QJsonWebToken::setToken(QString strToken) +bool QJsonWebToken::setToken(const QString &strToken) { // assume base64 encoded at first, if not try decoding bool isBase64Encoded = true; @@ -211,11 +529,13 @@ bool QJsonWebToken::setToken(QString strToken) { return false; } + m_byteHeader = fromBase64Url(listJwtParts.at(0).toUtf8()); + m_bytePayload = fromBase64Url(listJwtParts.at(1).toUtf8()); // check all parts are valid using another instance, // so we dont overwrite this instance in case of error QJsonWebToken tempTokenObj; - if ( !tempTokenObj.setHeaderQStr(QByteArray::fromBase64(listJwtParts.at(0).toUtf8())) || - !tempTokenObj.setPayloadQStr(QByteArray::fromBase64(listJwtParts.at(1).toUtf8())) ) + if ( !tempTokenObj.setHeaderQStr(m_byteHeader) || + !tempTokenObj.setPayloadQStr(m_bytePayload) ) { // try unencoded if (!tempTokenObj.setHeaderQStr(listJwtParts.at(0)) || @@ -226,90 +546,159 @@ bool QJsonWebToken::setToken(QString strToken) else { isBase64Encoded = false; + m_byteHeader = listJwtParts.at(0).toUtf8(); + m_bytePayload = listJwtParts.at(1).toUtf8(); } } // set parts on this instance - setHeaderQStr(tempTokenObj.getHeaderQStr()); - setPayloadQStr(tempTokenObj.getPayloadQStr()); + setHeaderJson(tempTokenObj.getHeaderJson()); + setPayloadJson(tempTokenObj.getPayloadJson()); + // set specified signature if (isBase64Encoded) { // unencode - m_byteSignature = QByteArray::fromBase64(listJwtParts.at(2).toUtf8()); + m_byteSignature = fromBase64Url(listJwtParts.at(2).toUtf8()); } else { m_byteSignature = listJwtParts.at(2).toUtf8(); } - // allData not valid anymore - m_byteAllData.clear(); // success - return true; -} - -QString QJsonWebToken::getRandAlphanum() -{ - return m_strRandAlphanum; -} - -void QJsonWebToken::setRandAlphanum(QString strRandAlphanum) -{ - if(strRandAlphanum.isNull()) - { - return; - } - m_strRandAlphanum = strRandAlphanum; -} - -int QJsonWebToken::getRandLength() -{ - return m_intRandLength; + return true; } -void QJsonWebToken::setRandLength(int intRandLength) +bool QJsonWebToken::isValid() const { - if(intRandLength < 0 || intRandLength > 1e6) - { - return; - } - m_intRandLength = intRandLength; + return isValidSignature() && isValidJson(); } -bool QJsonWebToken::isValid() +bool QJsonWebToken::isValidSignature() const { - // calculate token on other instance, - // so we dont overwrite this instance's signature - QJsonWebToken tempTokenObj = *this; - if (m_byteSignature != tempTokenObj.getSignature()) + if (m_jwk.isNull()) { return false; } - return true; + else + { + return m_jwk->verify(getAlgorithmStr(), m_byteSignature, m_byteAllData); + } +} + +bool QJsonWebToken::isValidJson() const +{ + QJsonParseError headerError, payloadError; + QJsonDocument header = QJsonDocument::fromJson(m_byteHeader, &headerError); + QJsonDocument payload = QJsonDocument::fromJson(m_bytePayload, &payloadError); + // TODO: RFC7515 Section 5.2 Message Signature o MAC Validation + return (! header.isNull() && ! payload.isNull() && headerError.error == QJsonParseError::NoError && payloadError.error == QJsonParseError::NoError); } -QJsonWebToken QJsonWebToken::fromTokenAndSecret(QString strToken, QString srtSecret) +QJsonWebToken QJsonWebToken::fromTokenAndKey(const QString &strToken, const QSharedPointer &key) { QJsonWebToken tempTokenObj; + // set Secret first + tempTokenObj.setKey(key); // set Token tempTokenObj.setToken(strToken); - // set Secret - tempTokenObj.setSecret(srtSecret); // return return tempTokenObj; } -void QJsonWebToken::appendClaim(QString strClaimType, QString strValue) +void QJsonWebToken::appendClaim(const QString &strClaimType, const QJsonValue &value) { // have to make a copy of the json object, modify the copy and then put it back, sigh QJsonObject jObj = m_jdocPayload.object(); - jObj.insert(strClaimType, strValue); - m_jdocPayload = QJsonDocument(jObj); + jObj.insert(strClaimType, value); + setPayloadJDoc(QJsonDocument(jObj)); } -void QJsonWebToken::removeClaim(QString strClaimType) +void QJsonWebToken::removeClaim(const QString &strClaimType) { // have to make a copy of the json object, modify the copy and then put it back, sigh QJsonObject jObj = m_jdocPayload.object(); jObj.remove(strClaimType); - m_jdocPayload = QJsonDocument(jObj); + setPayloadJDoc(QJsonDocument(jObj)); +} + +bool QJsonWebToken::isValidHeader(const QJsonDocument &jdocHeader) +{ + return ! jdocHeader.isEmpty() && ! jdocHeader.isNull() && jdocHeader.isObject() && isAlgorithmSupported(jdocHeader.object().value("alg").toString("")); +} + +bool QJsonWebToken::isValidHeader(const QByteArray &byteHeader) +{ + QJsonParseError error; + return isValidHeader(QJsonDocument::fromJson(byteHeader, &error)) && error.error == QJsonParseError::NoError; +} + +bool QJsonWebToken::isValidPayload(const QJsonDocument &jdocPayload) +{ + return ! jdocPayload.isEmpty() && ! jdocPayload.isNull() && jdocPayload.isObject(); +} + +bool QJsonWebToken::isValidPayload(const QByteArray &bytePayload) +{ + QJsonParseError error; + return isValidPayload(QJsonDocument::fromJson(bytePayload, &error)) && error.error == QJsonParseError::NoError; +} + +void QJsonWebToken::updateHeader(const QJsonDocument &jdocHeader) +{ + //assert(isValidHeader(jdocHeader)); + m_jdocHeader = jdocHeader; + m_byteHeader = jdocHeader.toJson(QJsonDocument::JsonFormat::Compact); + updateHeaderAlgorithm(); + updateSignature(); +} + +void QJsonWebToken::updateHeader(const QByteArray &byteHeader) +{ + //assert(isValidHeader(byteHeader)); + m_byteHeader = byteHeader; + m_jdocHeader = QJsonDocument::fromJson(byteHeader); + updateHeaderAlgorithm(); + updateSignature(); +} + +void QJsonWebToken::updateHeaderAlgorithm() +{ + //assert(isValidHeader(byteHeader)); + // set also new algorithm + m_strAlgorithm = m_jdocHeader.object().value("alg").toString(""); +} + +void QJsonWebToken::updatePayload(const QJsonDocument &jdocPayload) +{ + //assert(isValidPayload(jdocPayload)); + m_jdocPayload = jdocPayload; + m_bytePayload = jdocPayload.toJson(QJsonDocument::JsonFormat::Compact); + updateSignature(); +} + +void QJsonWebToken::updatePayload(const QByteArray &bytePayload) +{ + //assert(isValidPayload(bytePayload)); + m_bytePayload = bytePayload; + m_jdocPayload = QJsonDocument::fromJson(bytePayload); + updateSignature(); +} + +void QJsonWebToken::updateSignature() +{ + // recalculate + // get header in compact mode and base64 encoded + QByteArray byteHeaderBase64 = toBase64Url(m_byteHeader); + // get payload in compact mode and base64 encoded + QByteArray bytePayloadBase64 = toBase64Url(m_bytePayload); + // calculate signature based on chosen algorithm and secret + m_byteAllData = byteHeaderBase64 + "." + bytePayloadBase64; + if (m_jwk.isNull() || !m_jwk->isPrivate()) + { + m_byteSignature = QByteArray(); + } + else + { + m_byteSignature = m_jwk->sign(getAlgorithmStr(), m_byteAllData); + } } bool QJsonWebToken::isAlgorithmSupported(QString strAlgorithm) @@ -317,10 +706,14 @@ bool QJsonWebToken::isAlgorithmSupported(QString strAlgorithm) // TODO : support other algorithms if (strAlgorithm.compare("HS256", Qt::CaseInsensitive) != 0 && // HMAC using SHA-256 hash algorithm strAlgorithm.compare("HS384", Qt::CaseInsensitive) != 0 && // HMAC using SHA-384 hash algorithm - strAlgorithm.compare("HS512", Qt::CaseInsensitive) != 0 /*&& // HMAC using SHA-512 hash algorithm + strAlgorithm.compare("HS512", Qt::CaseInsensitive) != 0 // HMAC using SHA-512 hash algorithm +#ifdef USE_QCA + && strAlgorithm.compare("RS256", Qt::CaseInsensitive) != 0 && // RSA using SHA-256 hash algorithm strAlgorithm.compare("RS384", Qt::CaseInsensitive) != 0 && // RSA using SHA-384 hash algorithm - strAlgorithm.compare("RS512", Qt::CaseInsensitive) != 0 && // RSA using SHA-512 hash algorithm + strAlgorithm.compare("RS512", Qt::CaseInsensitive) != 0 // RSA using SHA-512 hash algorithm +#endif // USE_QCA + /*&& strAlgorithm.compare("ES256", Qt::CaseInsensitive) != 0 && // ECDSA using P-256 curve and SHA-256 hash algorithm strAlgorithm.compare("ES384", Qt::CaseInsensitive) != 0 && // ECDSA using P-384 curve and SHA-384 hash algorithm strAlgorithm.compare("ES512", Qt::CaseInsensitive) != 0*/) // ECDSA using P-521 curve and SHA-512 hash algorithm @@ -333,5 +726,10 @@ bool QJsonWebToken::isAlgorithmSupported(QString strAlgorithm) QStringList QJsonWebToken::supportedAlgorithms() { // TODO : support other algorithms - return QStringList() << "HS256" << "HS384" << "HS512"; + return QStringList() + << "HS256" << "HS384" << "HS512" +#ifdef USE_QCA + << "RS256" << "RS384" << "RS512" +#endif // USE_QCA + ; } diff --git a/src/qjsonwebtoken.h b/src/qjsonwebtoken.h index 63482c0..83e217b 100644 --- a/src/qjsonwebtoken.h +++ b/src/qjsonwebtoken.h @@ -17,9 +17,19 @@ #include #include +#include #include #include +#ifdef USE_QCA +#include +#endif // USE_QCA + + +// forward declaration +class QJsonWebKey; + + /** \brief QJsonWebToken : JWT (JSON Web Token) Implementation in Qt C++ @@ -69,7 +79,7 @@ class QJsonWebToken and empty *secret*. */ - QJsonWebToken(); // TODO : improve with params + QJsonWebToken(); // TODO : improve with params /** @@ -80,7 +90,7 @@ class QJsonWebToken Copies to the new instance the JWT *header*, *payload*, *signature*, *secret* and *algorithm*. */ - QJsonWebToken(const QJsonWebToken &other); + QJsonWebToken(const QJsonWebToken &other) = default; /** @@ -99,7 +109,8 @@ class QJsonWebToken Format can be *QJsonDocument::JsonFormat::Indented* or *QJsonDocument::JsonFormat::Compact* */ - QString getHeaderQStr(QJsonDocument::JsonFormat format = QJsonDocument::JsonFormat::Indented); + QString getHeaderQStr(bool pretty=true) const; + QByteArray getHeaderJson() const; /** @@ -110,7 +121,7 @@ class QJsonWebToken This method checks for a valid header format and returns false if the header is invalid. */ - bool setHeaderJDoc(QJsonDocument jdocHeader); + bool setHeaderJDoc(const QJsonDocument &jdocHeader); /** @@ -122,6 +133,7 @@ class QJsonWebToken */ bool setHeaderQStr(QString strHeader); + bool setHeaderJson(const QByteArray &jsonHeader); /** @@ -140,7 +152,8 @@ class QJsonWebToken Format can be *QJsonDocument::JsonFormat::Indented* or *QJsonDocument::JsonFormat::Compact* */ - QString getPayloadQStr(QJsonDocument::JsonFormat format = QJsonDocument::JsonFormat::Indented); + QString getPayloadQStr(bool pretty=true) const; + QByteArray getPayloadJson() const; /** @@ -151,7 +164,7 @@ class QJsonWebToken This method checks for a valid payload format and returns false if the payload is invalid. */ - bool setPayloadJDoc(QJsonDocument jdocPayload); + bool setPayloadJDoc(const QJsonDocument &jdocPayload); /** @@ -162,67 +175,41 @@ class QJsonWebToken This method checks for a valid payload format and returns false if the payload is invalid. */ - bool setPayloadQStr(QString strPayload); + bool setPayloadQStr(const QString &strPayload); + bool setPayloadJson(const QByteArray &jsonPayload); /** \brief Returns the JWT *signature* as a QByteArray. \return JWT *signature* as a decoded QByteArray. - - Recalculates the JWT signature given the current *header*, *payload*, *algorithm* and - *secret*. - - \warning This method overwrites the old signature internally. This could be undesired when - the signature was obtained by copying from another QJsonWebToken using the copy constructor. - */ - QByteArray getSignature(); // WARNING overwrites signature + QByteArray getSignature() const; /** \brief Returns the JWT *signature* as a QByteArray. \return JWT *signature* as a **base64 encoded** QByteArray. - - Recalculates the JWT signature given the current *header*, *payload*, *algorithm* and - *secret*. Then encodes the calculated signature using base64 encoding. - - \warning This method overwrites the old signature internally. This could be undesired when - the signature was obtained by copying from another QJsonWebToken using the copy constructor. - - */ - QByteArray getSignatureBase64(); // WARNING overwrites signature - - /** - - \brief Returns the JWT *secret* as a QString. - \return JWT *secret* as a QString. - */ - QString getSecret(); + QByteArray getSignatureBase64() const; /** - \brief Sets the JWT *secret* from a QString. - \param strSecret JWT *secret* as a QString. - \return true if the secret was set, false if the secret was not set. - - This method checks for a valid secret format and returns false if the secret is invalid. + \brief Returns the JWK. + \return JsonWebKey. */ - bool setSecret(QString strSecret); + QSharedPointer getKey() const; /** - \brief Creates and sets a random secret. + \brief Sets the JWK. + \param key JsonWebKey. + \return true if the key was set, false if the key was not set. - This method creates a random secret with the length defined by QJsonWebToken::getRandLength(), - and the characters defined by QJsonWebToken::getRandAlphanum(). - - \sa QJsonWebToken::getRandLength(). - \sa QJsonWebToken::getRandAlphanum(). + This method checks for a valid key format and returns false if the key is invalid. */ - void setRandomSecret(); + bool setKey(const QSharedPointer &key); /** @@ -230,7 +217,7 @@ class QJsonWebToken \return JWT *algorithm* as a QString. */ - QString getAlgorithmStr(); + QString getAlgorithmStr() const; /** @@ -245,7 +232,7 @@ class QJsonWebToken \sa QJsonWebToken::supportedAlgorithms(). */ - bool setAlgorithmStr(QString strAlgorithm); + bool setAlgorithmStr(const QString &strAlgorithm); /** @@ -265,7 +252,7 @@ class QJsonWebToken - *zzzzz* is the *signature* enconded in base64. */ - QString getToken(); + QString getToken() const; /** @@ -279,55 +266,7 @@ class QJsonWebToken \sa QJsonWebToken::getToken(). */ - bool setToken(QString strToken); - - /** - - \brief Returns the current set of characters used to create random secrets. - \return Set of characters as a QString. - - The default value is "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - - \sa QJsonWebToken::setRandomSecret() - \sa QJsonWebToken::setRandAlphanum() - - */ - QString getRandAlphanum(); - - /** - - \brief Sets the current set of characters used to create random secrets. - \param strRandAlphanum Set of characters as a QString. - - \sa QJsonWebToken::setRandomSecret() - \sa QJsonWebToken::getRandAlphanum() - - */ - void setRandAlphanum(QString strRandAlphanum); - - /** - - \brief Returns the current length used to create random secrets. - \return Length of random secret as a QString. - - The default value is 10; - - \sa QJsonWebToken::setRandomSecret() - \sa QJsonWebToken::setRandLength() - - */ - int getRandLength(); - - /** - - \brief Sets the current length used to create random secrets. - \param intRandLength Length of random secret. - - \sa QJsonWebToken::setRandomSecret() - \sa QJsonWebToken::getRandLength() - - */ - void setRandLength(int intRandLength); + bool setToken(const QString &strToken); /** @@ -339,20 +278,22 @@ class QJsonWebToken false is returned. */ - bool isValid(); + bool isValid() const; + bool isValidSignature() const; + bool isValidJson() const; /** \brief Creates a QJsonWebToken instance from the complete JWT and a secret. \param strToken Complete JWT as a QString. - \param srtSecret Secret as a QString. + \param secret Secret as a QByteArray. \return Instance of QJsonWebToken. The JWT provided must have a valid format, else a QJsonWebToken instance with default values will be returned. */ - static QJsonWebToken fromTokenAndSecret(QString strToken, QString srtSecret); + static QJsonWebToken fromTokenAndKey(const QString &strToken, const QSharedPointer &key); /** @@ -372,7 +313,7 @@ class QJsonWebToken claim value is updated. */ - void appendClaim(QString strClaimType, QString strValue); + void appendClaim(const QString &strClaimType, const QJsonValue &value); /** @@ -382,23 +323,71 @@ class QJsonWebToken If the claim type does not exist in the *payload*, then this method does nothins. */ - void removeClaim(QString strClaimType); + void removeClaim(const QString &strClaimType); + + static bool isValidHeader(const QJsonDocument &jdocHeader); + static bool isValidHeader(const QByteArray &byteHeader); + static bool isValidPayload(const QJsonDocument &jdocPayload); + static bool isValidPayload(const QByteArray &bytePayload); + +protected: + void updateHeader(const QJsonDocument &jdocHeader); + void updateHeader(const QByteArray &byteHeader); + void updateHeaderAlgorithm(); + void updatePayload(const QJsonDocument &jdocPayload); + void updatePayload(const QByteArray &bytePayload); + void updateSignature(); private: // properties + QByteArray m_byteHeader; // original + QByteArray m_bytePayload; // original QJsonDocument m_jdocHeader; // unencoded QJsonDocument m_jdocPayload; // unencoded QByteArray m_byteSignature; // unencoded - QString m_strSecret; QString m_strAlgorithm; - int m_intRandLength ; - QString m_strRandAlphanum; - // helpers QByteArray m_byteAllData; - bool isAlgorithmSupported(QString strAlgorithm); + QSharedPointer m_jwk; + + static bool isAlgorithmSupported(QString strAlgorithm); +}; + + +class QJsonWebKey +{ +public: + typedef enum { + Octet, + RSA, + EC, + } KeyType; + + virtual ~QJsonWebKey() + {} + + virtual KeyType type() const = 0; + virtual bool isPrivate() const = 0; + virtual QByteArray sign(const QString &algorithm, const QByteArray &data) const = 0; + virtual bool verify(const QString &algorithm, const QByteArray &signature, const QByteArray &data) const = 0; + + virtual QSharedPointer toPublic() = 0; + virtual QByteArray toJson() const = 0; + + /** + + \brief Returns a list of the supported algorithms. + \return List of supported algorithms as a QStringList. + + */ + virtual QStringList supportedAlgorithms() const = 0; + + static QSharedPointer fromJsonWebKey(const QByteArray &jwk); + static QSharedPointer fromOctet(const QByteArray &data); + static QSharedPointer generateRSAPrivateKey(int bits); }; + #endif // QJSONWEBTOKEN_H