crypto_signatures.c (10778B)
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 build_message (const struct TALER_PosChallengeP *challenge, 55 unsigned char *msg) 56 { 57 memcpy (msg, 58 TALER_POS_CHALLENGE_SALT, 59 sizeof (TALER_POS_CHALLENGE_SALT) - 1); 60 memcpy (&msg[sizeof (TALER_POS_CHALLENGE_SALT) - 1], 61 challenge->challenge, 62 TALER_POS_CHALLENGE_LENGTH); 63 } 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 hash_message (const struct TALER_PosChallengeP *challenge, 74 struct GNUNET_ShortHashCode *hash) 75 { 76 unsigned char msg[CHALLENGE_MSG_SIZE]; 77 78 build_message (challenge, 79 msg); 80 gcry_md_hash_buffer (GCRY_MD_SHA256, 81 hash, 82 msg, 83 sizeof (msg)); 84 } 85 86 87 enum GNUNET_GenericReturnValue 88 TALER_otp_device_key_create ( 89 enum TALER_MerchantConfirmationAlgorithm pos_alg, 90 char **pos_key, 91 char **pos_pub) 92 { 93 *pos_key = NULL; 94 *pos_pub = NULL; 95 switch (pos_alg) 96 { 97 case TALER_MCA_ECDSA_CHALLENGE: 98 { 99 struct TALER_EcdsaP256PrivateKeyP priv; 100 struct TALER_EcdsaP256PublicKeyP pub; 101 102 if (GNUNET_OK != 103 TALER_ecdsa_p256_key_create (&priv, 104 &pub)) 105 return GNUNET_SYSERR; 106 *pos_key = GNUNET_STRINGS_data_to_string_alloc (&priv, 107 sizeof (priv)); 108 *pos_pub = GNUNET_STRINGS_data_to_string_alloc (&pub, 109 sizeof (pub)); 110 GNUNET_CRYPTO_zero_keys (&priv, 111 sizeof (priv)); 112 break; 113 } 114 case TALER_MCA_EDDSA_CHALLENGE: 115 { 116 struct GNUNET_CRYPTO_EddsaPrivateKey priv; 117 struct GNUNET_CRYPTO_EddsaPublicKey pub; 118 119 GNUNET_CRYPTO_eddsa_key_create (&priv); 120 GNUNET_CRYPTO_eddsa_key_get_public (&priv, 121 &pub); 122 *pos_key = GNUNET_STRINGS_data_to_string_alloc (&priv, 123 sizeof (priv)); 124 *pos_pub = GNUNET_STRINGS_data_to_string_alloc (&pub, 125 sizeof (pub)); 126 GNUNET_CRYPTO_eddsa_key_clear (&priv); 127 break; 128 } 129 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 GNUNET_break (0); 134 return GNUNET_SYSERR; 135 } 136 if ( (NULL == *pos_key) || 137 (NULL == *pos_pub) ) 138 { 139 GNUNET_break (0); 140 GNUNET_free (*pos_key); 141 GNUNET_free (*pos_pub); 142 return GNUNET_SYSERR; 143 } 144 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 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 if ( (NULL == pos_key) || 165 (NULL == challenge) ) 166 { 167 GNUNET_break (0); 168 return NULL; 169 } 170 if (GNUNET_OK != 171 GNUNET_STRINGS_string_to_data (pos_key, 172 strlen (pos_key), 173 &priv, 174 sizeof (priv))) 175 { 176 GNUNET_break (0); 177 return NULL; 178 } 179 hash_message (challenge, 180 &hash); 181 if (GNUNET_OK != 182 TALER_ecdsa_p256_sign (&priv, 183 &hash, 184 &sig)) 185 { 186 GNUNET_break (0); 187 GNUNET_CRYPTO_zero_keys (&priv, 188 sizeof (priv)); 189 return NULL; 190 } 191 GNUNET_CRYPTO_zero_keys (&priv, 192 sizeof (priv)); 193 ret = GNUNET_STRINGS_data_to_string_alloc (&sig, 194 sizeof (sig)); 195 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 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 if ( (NULL == pos_key) || 216 (NULL == challenge) ) 217 { 218 GNUNET_break (0); 219 return NULL; 220 } 221 if (GNUNET_OK != 222 GNUNET_STRINGS_string_to_data (pos_key, 223 strlen (pos_key), 224 &priv, 225 sizeof (priv))) 226 { 227 GNUNET_break (0); 228 return NULL; 229 } 230 build_message (challenge, 231 msg); 232 if (GNUNET_OK != 233 GNUNET_CRYPTO_eddsa_sign_raw (&priv, 234 msg, 235 sizeof (msg), 236 &sig)) 237 { 238 GNUNET_break (0); 239 GNUNET_CRYPTO_eddsa_key_clear (&priv); 240 return NULL; 241 } 242 GNUNET_CRYPTO_eddsa_key_clear (&priv); 243 ret = GNUNET_STRINGS_data_to_string_alloc (&sig, 244 sizeof (sig)); 245 return ret; 246 } 247 248 249 char * 250 TALER_build_pos_confirmation_sig ( 251 const char *pos_key, 252 enum TALER_MerchantConfirmationAlgorithm pos_alg, 253 const struct TALER_PosChallengeP *challenge) 254 { 255 if ( (NULL == pos_key) || 256 (NULL == challenge) ) 257 { 258 GNUNET_break (0); 259 return NULL; 260 } 261 switch (pos_alg) 262 { 263 case TALER_MCA_ECDSA_CHALLENGE: 264 return sign_ecdsa (pos_key, 265 challenge); 266 case TALER_MCA_EDDSA_CHALLENGE: 267 return sign_eddsa (pos_key, 268 challenge); 269 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 GNUNET_break (0); 274 return NULL; 275 } 276 GNUNET_break (0); 277 return NULL; 278 } 279 280 281 enum GNUNET_GenericReturnValue 282 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 if ( (NULL == pos_pub) || 289 (NULL == challenge) || 290 (NULL == pos_confirmation) ) 291 { 292 GNUNET_break (0); 293 return GNUNET_SYSERR; 294 } 295 switch (pos_alg) 296 { 297 case TALER_MCA_ECDSA_CHALLENGE: 298 { 299 struct TALER_EcdsaP256PublicKeyP pub; 300 struct TALER_EcdsaP256SignatureP sig; 301 struct GNUNET_ShortHashCode hash; 302 303 if ( (GNUNET_OK != 304 GNUNET_STRINGS_string_to_data (pos_pub, 305 strlen (pos_pub), 306 &pub, 307 sizeof (pub))) || 308 (GNUNET_OK != 309 GNUNET_STRINGS_string_to_data (pos_confirmation, 310 strlen (pos_confirmation), 311 &sig, 312 sizeof (sig))) ) 313 { 314 GNUNET_break_op (0); 315 return GNUNET_SYSERR; 316 } 317 hash_message (challenge, 318 &hash); 319 return TALER_ecdsa_p256_verify (&pub, 320 &hash, 321 &sig); 322 } 323 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 if ( (GNUNET_OK != 330 GNUNET_STRINGS_string_to_data (pos_pub, 331 strlen (pos_pub), 332 &pub, 333 sizeof (pub))) || 334 (GNUNET_OK != 335 GNUNET_STRINGS_string_to_data (pos_confirmation, 336 strlen (pos_confirmation), 337 &sig, 338 sizeof (sig))) ) 339 { 340 GNUNET_break_op (0); 341 return GNUNET_SYSERR; 342 } 343 build_message (challenge, 344 msg); 345 return GNUNET_CRYPTO_eddsa_verify_raw (msg, 346 sizeof (msg), 347 &sig, 348 &pub); 349 } 350 case TALER_MCA_NONE: 351 case TALER_MCA_WITHOUT_PRICE: 352 case TALER_MCA_WITH_PRICE: 353 GNUNET_break (0); 354 return GNUNET_SYSERR; 355 } 356 GNUNET_break (0); 357 return GNUNET_SYSERR; 358 } 359 360 361 /* end of crypto_signatures.c */