1 /*
2  *  Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
3  *
4  *  This program is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 2 or (at your option)
7  *  version 3 of the License.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "Signature.h"
19 #include "core/Tools.h"
20 #include "crypto/Crypto.h"
21 #include "crypto/CryptoHash.h"
22 #include "crypto/ssh/OpenSSHKey.h"
23 
24 #include <QByteArray>
25 #include <gcrypt.h>
26 
27 struct RSASigner
28 {
29     gcry_error_t rc;
30     QString error;
31 
raiseErrorRSASigner32     void raiseError(const QString& message = QString())
33     {
34         if (message.isEmpty()) {
35             error = QString("%1/%2").arg(QString::fromLocal8Bit(gcry_strsource(rc)),
36                                          QString::fromLocal8Bit(gcry_strerror(rc)));
37         } else {
38             error = message;
39         }
40     }
41 
RSASignerRSASigner42     RSASigner()
43         : rc(GPG_ERR_NO_ERROR)
44     {
45     }
46 
signRSASigner47     QString sign(const QByteArray& data, const OpenSSHKey& key)
48     {
49         enum Index
50         {
51             N,
52             E,
53             D,
54             P,
55             Q,
56             U, // private key
57             R,
58             S, // signature
59 
60             Data,
61             Key,
62             Sig
63         };
64 
65         const QList<QByteArray> parts = key.privateParts();
66         if (parts.count() != 6) {
67             raiseError("Unsupported signing key");
68             return QString();
69         }
70 
71         const QByteArray block = CryptoHash::hash(data, CryptoHash::Sha256);
72 
73         Tools::Map<Index, gcry_mpi_t, &gcry_mpi_release> mpi;
74         Tools::Map<Index, gcry_sexp_t, &gcry_sexp_release> sexp;
75         const gcry_mpi_format format = GCRYMPI_FMT_USG;
76         rc = gcry_mpi_scan(&mpi[N], format, parts[0].data(), parts[0].size(), nullptr);
77         if (rc != GPG_ERR_NO_ERROR) {
78             raiseError();
79             return QString();
80         }
81         rc = gcry_mpi_scan(&mpi[E], format, parts[1].data(), parts[1].size(), nullptr);
82         if (rc != GPG_ERR_NO_ERROR) {
83             raiseError();
84             return QString();
85         }
86         rc = gcry_mpi_scan(&mpi[D], format, parts[2].data(), parts[2].size(), nullptr);
87         if (rc != GPG_ERR_NO_ERROR) {
88             raiseError();
89             return QString();
90         }
91         rc = gcry_mpi_scan(&mpi[U], format, parts[3].data(), parts[3].size(), nullptr);
92         if (rc != GPG_ERR_NO_ERROR) {
93             raiseError();
94             return QString();
95         }
96         rc = gcry_mpi_scan(&mpi[P], format, parts[4].data(), parts[4].size(), nullptr);
97         if (rc != GPG_ERR_NO_ERROR) {
98             raiseError();
99             return QString();
100         }
101         rc = gcry_mpi_scan(&mpi[Q], format, parts[5].data(), parts[5].size(), nullptr);
102         if (rc != GPG_ERR_NO_ERROR) {
103             raiseError();
104             return QString();
105         }
106         if (gcry_mpi_cmp(mpi[P], mpi[Q]) > 0) {
107             // see https://www.gnupg.org/documentation/manuals/gcrypt/RSA-key-parameters.html#RSA-key-parameters
108             gcry_mpi_swap(mpi[P], mpi[Q]);
109             gcry_mpi_invm(mpi[U], mpi[P], mpi[Q]);
110         }
111         rc = gcry_sexp_build(&sexp[Key],
112                              NULL,
113                              "(private-key (rsa (n %m) (e %m) (d %m) (p %m) (q %m) (u %m)))",
114                              mpi[N],
115                              mpi[E],
116                              mpi[D],
117                              mpi[P],
118                              mpi[Q],
119                              mpi[U]);
120         if (rc != GPG_ERR_NO_ERROR) {
121             raiseError();
122             return QString();
123         }
124 
125         rc = gcry_pk_testkey(sexp[Key]);
126         if (rc != GPG_ERR_NO_ERROR) {
127             raiseError();
128             return QString();
129         }
130 
131         rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags pkcs1) (hash sha256 %b))", block.size(), block.data());
132         // rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags raw) (value %b))", data.size(), data.data());
133         if (rc != GPG_ERR_NO_ERROR) {
134             raiseError();
135             return QString();
136         }
137         rc = gcry_pk_sign(&sexp[Sig], sexp[Data], sexp[Key]);
138         if (rc != GPG_ERR_NO_ERROR) {
139             raiseError();
140             return QString();
141         }
142         sexp[S] = gcry_sexp_find_token(sexp[Sig], "s", 1);
143         mpi[S] = gcry_sexp_nth_mpi(sexp[S], 1, GCRYMPI_FMT_USG);
144         Tools::Buffer buffer;
145         rc = gcry_mpi_aprint(GCRYMPI_FMT_STD, &buffer.raw, &buffer.size, mpi[S]);
146         if (rc != GPG_ERR_NO_ERROR) {
147             raiseError();
148             return QString();
149         }
150         return QString("rsa|%1").arg(QString::fromLatin1(buffer.content().toHex()));
151     }
152 
verifyRSASigner153     bool verify(const QByteArray& data, const OpenSSHKey& key, const QString& signature)
154     {
155         const gcry_mpi_format format = GCRYMPI_FMT_USG;
156         enum MPI
157         {
158             N,
159             E, // public key
160             R,
161             S // signature
162         };
163         enum SEXP
164         {
165             Data,
166             Key,
167             Sig
168         };
169 
170         const QList<QByteArray> parts = key.publicParts();
171         if (parts.count() != 2) {
172             raiseError("Unsupported verification key");
173             return false;
174         }
175 
176         const QByteArray block = CryptoHash::hash(data, CryptoHash::Sha256);
177 
178         Tools::Map<MPI, gcry_mpi_t, &gcry_mpi_release> mpi;
179         Tools::Map<SEXP, gcry_sexp_t, &gcry_sexp_release> sexp;
180 
181         rc = gcry_mpi_scan(&mpi[E], format, parts[0].data(), parts[0].size(), nullptr);
182         if (rc != GPG_ERR_NO_ERROR) {
183             raiseError();
184             return false;
185         }
186         rc = gcry_mpi_scan(&mpi[N], format, parts[1].data(), parts[1].size(), nullptr);
187         if (rc != GPG_ERR_NO_ERROR) {
188             raiseError();
189             return false;
190         }
191         rc = gcry_sexp_build(&sexp[Key], NULL, "(public-key (rsa (n %m) (e %m)))", mpi[N], mpi[E]);
192         if (rc != GPG_ERR_NO_ERROR) {
193             raiseError();
194             return false;
195         }
196 
197         QRegExp extractor("rsa\\|([a-f0-9]+)", Qt::CaseInsensitive);
198         if (!extractor.exactMatch(signature) || extractor.captureCount() != 1) {
199             raiseError("Could not unpack signature parts");
200             return false;
201         }
202         const QByteArray sig_s = QByteArray::fromHex(extractor.cap(1).toLatin1());
203 
204         rc = gcry_mpi_scan(&mpi[S], GCRYMPI_FMT_STD, sig_s.data(), sig_s.size(), nullptr);
205         if (rc != GPG_ERR_NO_ERROR) {
206             raiseError();
207             return false;
208         }
209         rc = gcry_sexp_build(&sexp[Sig], NULL, "(sig-val (rsa (s %m)))", mpi[S]);
210         if (rc != GPG_ERR_NO_ERROR) {
211             raiseError();
212             return false;
213         }
214         rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags pkcs1) (hash sha256 %b))", block.size(), block.data());
215         // rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags raw) (value %b))", data.size(), data.data());
216         if (rc != GPG_ERR_NO_ERROR) {
217             raiseError();
218             return false;
219         }
220         rc = gcry_pk_verify(sexp[Sig], sexp[Data], sexp[Key]);
221         if (rc != GPG_ERR_NO_ERROR && rc != GPG_ERR_BAD_SIGNATURE) {
222             raiseError();
223             return false;
224         }
225         return rc != GPG_ERR_BAD_SIGNATURE;
226     }
227 };
228 
create(const QByteArray & data,const OpenSSHKey & key)229 QString Signature::create(const QByteArray& data, const OpenSSHKey& key)
230 {
231     // TODO HNH: currently we publish the signature in our own non-standard format - it would
232     //           be better to use a standard format (like ASN1 - but this would be more easy
233     //           when we integrate a proper library)
234     //           Even more, we could publish standard self signed certificates with the container
235     //           instead of the custom certificates
236     if (key.type() == "ssh-rsa") {
237         RSASigner signer;
238         QString result = signer.sign(data, key);
239         if (signer.rc != GPG_ERR_NO_ERROR) {
240             ::qWarning() << signer.error;
241         }
242         return result;
243     }
244     ::qWarning() << "Unsupported Public/Private key format";
245     return QString();
246 }
247 
verify(const QByteArray & data,const QString & signature,const OpenSSHKey & key)248 bool Signature::verify(const QByteArray& data, const QString& signature, const OpenSSHKey& key)
249 {
250     if (key.type() == "ssh-rsa") {
251         RSASigner signer;
252         bool result = signer.verify(data, key, signature);
253         if (signer.rc != GPG_ERR_NO_ERROR) {
254             ::qWarning() << signer.error;
255         }
256         return result;
257     }
258     ::qWarning() << "Unsupported Public/Private key format";
259     return false;
260 }
261