Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file util/crypto_signatures.c
18 : * @brief POS confirmations that sign a challenge (DD 97)
19 : * @author Bohdan Potuzhnyi
20 : * @author Volodymyr Potuzhnyi
21 : *
22 : * The counterpart to the time-based confirmations in
23 : * crypto_confirmation.c. An offline verifier picks an unpredictable
24 : * challenge, the merchant backend signs it once the order was paid,
25 : * and the verifier checks that signature against a public key it was
26 : * configured with. Only the public key ever leaves the backend.
27 : *
28 : * This file owns the message that gets signed, the encoding of keys
29 : * and signatures, and which curve each algorithm uses. The signature
30 : * operations themselves come from TALER_ecdsa_p256_*() and from
31 : * GNUnet's EdDSA.
32 : */
33 : #include "platform.h"
34 : #include "taler/taler_util.h"
35 : #include <gcrypt.h>
36 :
37 :
38 : /**
39 : * Size of the message that gets signed.
40 : */
41 : #define CHALLENGE_MSG_SIZE \
42 : (sizeof (TALER_POS_CHALLENGE_SALT) - 1 + TALER_POS_CHALLENGE_LENGTH)
43 :
44 :
45 : /**
46 : * Assemble the message a confirmation signs: the domain separation
47 : * salt directly followed by the raw challenge bytes. An offline
48 : * verifier reproduces exactly this.
49 : *
50 : * @param challenge challenge to bind the signature to
51 : * @param[out] msg where to write #CHALLENGE_MSG_SIZE bytes
52 : */
53 : static void
54 676 : build_message (const struct TALER_PosChallengeP *challenge,
55 : unsigned char *msg)
56 : {
57 676 : memcpy (msg,
58 : TALER_POS_CHALLENGE_SALT,
59 : sizeof (TALER_POS_CHALLENGE_SALT) - 1);
60 676 : memcpy (&msg[sizeof (TALER_POS_CHALLENGE_SALT) - 1],
61 676 : challenge->challenge,
62 : TALER_POS_CHALLENGE_LENGTH);
63 676 : }
64 :
65 :
66 : /**
67 : * Hash the challenge message, as the ECDSA algorithm signs a digest.
68 : *
69 : * @param challenge challenge to bind the signature to
70 : * @param[out] hash set to the SHA-256 of the challenge message
71 : */
72 : static void
73 663 : hash_message (const struct TALER_PosChallengeP *challenge,
74 : struct GNUNET_ShortHashCode *hash)
75 : {
76 : unsigned char msg[CHALLENGE_MSG_SIZE];
77 :
78 663 : build_message (challenge,
79 : msg);
80 663 : gcry_md_hash_buffer (GCRY_MD_SHA256,
81 : hash,
82 : msg,
83 : sizeof (msg));
84 663 : }
85 :
86 :
87 : enum GNUNET_GenericReturnValue
88 314 : TALER_otp_device_key_create (
89 : enum TALER_MerchantConfirmationAlgorithm pos_alg,
90 : char **pos_key,
91 : char **pos_pub)
92 : {
93 314 : *pos_key = NULL;
94 314 : *pos_pub = NULL;
95 314 : switch (pos_alg)
96 : {
97 306 : case TALER_MCA_ECDSA_CHALLENGE:
98 : {
99 : struct TALER_EcdsaP256PrivateKeyP priv;
100 : struct TALER_EcdsaP256PublicKeyP pub;
101 :
102 306 : if (GNUNET_OK !=
103 306 : TALER_ecdsa_p256_key_create (&priv,
104 : &pub))
105 0 : return GNUNET_SYSERR;
106 306 : *pos_key = GNUNET_STRINGS_data_to_string_alloc (&priv,
107 : sizeof (priv));
108 306 : *pos_pub = GNUNET_STRINGS_data_to_string_alloc (&pub,
109 : sizeof (pub));
110 306 : GNUNET_CRYPTO_zero_keys (&priv,
111 : sizeof (priv));
112 306 : break;
113 : }
114 5 : case TALER_MCA_EDDSA_CHALLENGE:
115 : {
116 : struct GNUNET_CRYPTO_EddsaPrivateKey priv;
117 : struct GNUNET_CRYPTO_EddsaPublicKey pub;
118 :
119 5 : GNUNET_CRYPTO_eddsa_key_create (&priv);
120 5 : GNUNET_CRYPTO_eddsa_key_get_public (&priv,
121 : &pub);
122 5 : *pos_key = GNUNET_STRINGS_data_to_string_alloc (&priv,
123 : sizeof (priv));
124 5 : *pos_pub = GNUNET_STRINGS_data_to_string_alloc (&pub,
125 : sizeof (pub));
126 5 : GNUNET_CRYPTO_eddsa_key_clear (&priv);
127 5 : break;
128 : }
129 3 : case TALER_MCA_NONE:
130 : case TALER_MCA_WITHOUT_PRICE:
131 : case TALER_MCA_WITH_PRICE:
132 : /* TOTP keys are supplied by the merchant, not generated here */
133 3 : GNUNET_break (0);
134 3 : return GNUNET_SYSERR;
135 : }
136 311 : if ( (NULL == *pos_key) ||
137 311 : (NULL == *pos_pub) )
138 : {
139 0 : GNUNET_break (0);
140 0 : GNUNET_free (*pos_key);
141 0 : GNUNET_free (*pos_pub);
142 0 : return GNUNET_SYSERR;
143 : }
144 311 : return GNUNET_OK;
145 : }
146 :
147 :
148 : /**
149 : * Sign @a challenge with the NIST P-256 private key @a pos_key.
150 : *
151 : * @param pos_key Crockford base32-encoded P-256 private scalar
152 : * @param challenge challenge to bind the signature to
153 : * @return Crockford base32-encoded r|s signature, or NULL on error
154 : */
155 : static char *
156 308 : sign_ecdsa (const char *pos_key,
157 : const struct TALER_PosChallengeP *challenge)
158 : {
159 : struct TALER_EcdsaP256PrivateKeyP priv;
160 : struct TALER_EcdsaP256SignatureP sig;
161 : struct GNUNET_ShortHashCode hash;
162 : char *ret;
163 :
164 308 : if ( (NULL == pos_key) ||
165 : (NULL == challenge) )
166 : {
167 0 : GNUNET_break (0);
168 0 : return NULL;
169 : }
170 308 : if (GNUNET_OK !=
171 308 : GNUNET_STRINGS_string_to_data (pos_key,
172 : strlen (pos_key),
173 : &priv,
174 : sizeof (priv)))
175 : {
176 1 : GNUNET_break (0);
177 1 : return NULL;
178 : }
179 307 : hash_message (challenge,
180 : &hash);
181 307 : if (GNUNET_OK !=
182 307 : TALER_ecdsa_p256_sign (&priv,
183 : &hash,
184 : &sig))
185 : {
186 0 : GNUNET_break (0);
187 0 : GNUNET_CRYPTO_zero_keys (&priv,
188 : sizeof (priv));
189 0 : return NULL;
190 : }
191 307 : GNUNET_CRYPTO_zero_keys (&priv,
192 : sizeof (priv));
193 307 : ret = GNUNET_STRINGS_data_to_string_alloc (&sig,
194 : sizeof (sig));
195 307 : return ret;
196 : }
197 :
198 :
199 : /**
200 : * Sign @a challenge with the Ed25519 private key @a pos_key.
201 : *
202 : * @param pos_key Crockford base32-encoded Ed25519 private key
203 : * @param challenge challenge to bind the signature to
204 : * @return Crockford base32-encoded signature, or NULL on error
205 : */
206 : static char *
207 8 : sign_eddsa (const char *pos_key,
208 : const struct TALER_PosChallengeP *challenge)
209 : {
210 : struct GNUNET_CRYPTO_EddsaPrivateKey priv;
211 : struct GNUNET_CRYPTO_EddsaSignature sig;
212 : unsigned char msg[CHALLENGE_MSG_SIZE];
213 : char *ret;
214 :
215 8 : if ( (NULL == pos_key) ||
216 : (NULL == challenge) )
217 : {
218 0 : GNUNET_break (0);
219 0 : return NULL;
220 : }
221 8 : if (GNUNET_OK !=
222 8 : GNUNET_STRINGS_string_to_data (pos_key,
223 : strlen (pos_key),
224 : &priv,
225 : sizeof (priv)))
226 : {
227 1 : GNUNET_break (0);
228 1 : return NULL;
229 : }
230 7 : build_message (challenge,
231 : msg);
232 7 : if (GNUNET_OK !=
233 7 : GNUNET_CRYPTO_eddsa_sign_raw (&priv,
234 : msg,
235 : sizeof (msg),
236 : &sig))
237 : {
238 0 : GNUNET_break (0);
239 0 : GNUNET_CRYPTO_eddsa_key_clear (&priv);
240 0 : return NULL;
241 : }
242 7 : GNUNET_CRYPTO_eddsa_key_clear (&priv);
243 7 : ret = GNUNET_STRINGS_data_to_string_alloc (&sig,
244 : sizeof (sig));
245 7 : return ret;
246 : }
247 :
248 :
249 : char *
250 318 : TALER_build_pos_confirmation_sig (
251 : const char *pos_key,
252 : enum TALER_MerchantConfirmationAlgorithm pos_alg,
253 : const struct TALER_PosChallengeP *challenge)
254 : {
255 318 : if ( (NULL == pos_key) ||
256 : (NULL == challenge) )
257 : {
258 2 : GNUNET_break (0);
259 2 : return NULL;
260 : }
261 316 : switch (pos_alg)
262 : {
263 308 : case TALER_MCA_ECDSA_CHALLENGE:
264 308 : return sign_ecdsa (pos_key,
265 : challenge);
266 8 : case TALER_MCA_EDDSA_CHALLENGE:
267 8 : return sign_eddsa (pos_key,
268 : challenge);
269 0 : case TALER_MCA_NONE:
270 : case TALER_MCA_WITHOUT_PRICE:
271 : case TALER_MCA_WITH_PRICE:
272 : /* time-based algorithms are TALER_build_pos_confirmation()'s job */
273 0 : GNUNET_break (0);
274 0 : return NULL;
275 : }
276 0 : GNUNET_break (0);
277 0 : return NULL;
278 : }
279 :
280 :
281 : enum GNUNET_GenericReturnValue
282 369 : TALER_check_pos_confirmation_sig (
283 : const char *pos_pub,
284 : enum TALER_MerchantConfirmationAlgorithm pos_alg,
285 : const struct TALER_PosChallengeP *challenge,
286 : const char *pos_confirmation)
287 : {
288 369 : if ( (NULL == pos_pub) ||
289 369 : (NULL == challenge) ||
290 : (NULL == pos_confirmation) )
291 : {
292 0 : GNUNET_break (0);
293 0 : return GNUNET_SYSERR;
294 : }
295 369 : switch (pos_alg)
296 : {
297 357 : case TALER_MCA_ECDSA_CHALLENGE:
298 : {
299 : struct TALER_EcdsaP256PublicKeyP pub;
300 : struct TALER_EcdsaP256SignatureP sig;
301 : struct GNUNET_ShortHashCode hash;
302 :
303 357 : if ( (GNUNET_OK !=
304 357 : GNUNET_STRINGS_string_to_data (pos_pub,
305 : strlen (pos_pub),
306 : &pub,
307 356 : sizeof (pub))) ||
308 : (GNUNET_OK !=
309 356 : GNUNET_STRINGS_string_to_data (pos_confirmation,
310 : strlen (pos_confirmation),
311 : &sig,
312 : sizeof (sig))) )
313 : {
314 1 : GNUNET_break_op (0);
315 1 : return GNUNET_SYSERR;
316 : }
317 356 : hash_message (challenge,
318 : &hash);
319 356 : return TALER_ecdsa_p256_verify (&pub,
320 : &hash,
321 : &sig);
322 : }
323 11 : case TALER_MCA_EDDSA_CHALLENGE:
324 : {
325 : struct GNUNET_CRYPTO_EddsaPublicKey pub;
326 : struct GNUNET_CRYPTO_EddsaSignature sig;
327 : unsigned char msg[CHALLENGE_MSG_SIZE];
328 :
329 11 : if ( (GNUNET_OK !=
330 11 : GNUNET_STRINGS_string_to_data (pos_pub,
331 : strlen (pos_pub),
332 : &pub,
333 8 : sizeof (pub))) ||
334 : (GNUNET_OK !=
335 8 : GNUNET_STRINGS_string_to_data (pos_confirmation,
336 : strlen (pos_confirmation),
337 : &sig,
338 : sizeof (sig))) )
339 : {
340 5 : GNUNET_break_op (0);
341 5 : return GNUNET_SYSERR;
342 : }
343 6 : build_message (challenge,
344 : msg);
345 6 : return GNUNET_CRYPTO_eddsa_verify_raw (msg,
346 : sizeof (msg),
347 : &sig,
348 : &pub);
349 : }
350 1 : case TALER_MCA_NONE:
351 : case TALER_MCA_WITHOUT_PRICE:
352 : case TALER_MCA_WITH_PRICE:
353 1 : GNUNET_break (0);
354 1 : return GNUNET_SYSERR;
355 : }
356 0 : GNUNET_break (0);
357 0 : return GNUNET_SYSERR;
358 : }
359 :
360 :
361 : /* end of crypto_signatures.c */
|