1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
|
/* md.c - message digest dispatcher
* Copyright (C) 1998,1999 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include "g10lib.h"
#include "cipher.h"
#include "dynload.h"
#include "rmd.h"
struct md_digest_list_s;
/* this structure is put right after the GCRY_MD_HD buffer, so that
* only one memory block is needed. */
struct gcry_md_context {
int magic;
int secure;
FILE *debug;
int finalized;
struct md_digest_list_s *list;
};
#define CTX_MAGIC_NORMAL 0x11071961
#define CTX_MAGIC_SECURE 0x16917011
static const char * digest_algo_to_string( int algo );
static int check_digest_algo( int algo );
static GCRY_MD_HD md_open( int algo, int secure );
static int md_enable( GCRY_MD_HD hd, int algo );
static GCRY_MD_HD md_copy( GCRY_MD_HD a );
static void md_close(GCRY_MD_HD a);
static void md_write( GCRY_MD_HD a, byte *inbuf, size_t inlen);
static void md_final(GCRY_MD_HD a);
static byte *md_read( GCRY_MD_HD a, int algo );
static int md_get_algo( GCRY_MD_HD a );
static int md_digest_length( int algo );
static const byte *md_asn_oid( int algo, size_t *asnlen, size_t *mdlen );
static void md_start_debug( GCRY_MD_HD a, const char *suffix );
static void md_stop_debug( GCRY_MD_HD a );
/****************
* This structure is used for the list of available algorithms
* and for the list of algorithms in GCRY_MD_HD.
*/
struct md_digest_list_s {
struct md_digest_list_s *next;
const char *name;
int algo;
byte *asnoid;
int asnlen;
int mdlen;
void (*init)( void *c );
void (*write)( void *c, byte *buf, size_t nbytes );
void (*final)( void *c );
byte *(*read)( void *c );
size_t contextsize; /* allocate this amount of context */
PROPERLY_ALIGNED_TYPE context;
};
static struct md_digest_list_s *digest_list;
static struct md_digest_list_s *
new_list_item( int algo,
const char *(*get_info)( int, size_t*,byte**, int*, int*,
void (**)(void*),
void (**)(void*,byte*,size_t),
void (**)(void*),byte *(**)(void*)) )
{
struct md_digest_list_s *r;
r = g10_xcalloc( 1, sizeof *r );
r->algo = algo,
r->name = (*get_info)( algo, &r->contextsize,
&r->asnoid, &r->asnlen, &r->mdlen,
&r->init, &r->write, &r->final, &r->read );
if( !r->name ) {
g10_free(r);
r = NULL;
}
return r;
}
/****************
* Try to load the modules with the requeste algorithm
* and return true if new modules are available
* If req_alog is -1 try to load all digest algorithms.
*/
static int
load_digest_module( int req_algo )
{
static int initialized = 0;
static u32 checked_algos[256/32];
static int checked_all = 0;
struct md_digest_list_s *r;
void *context = NULL;
int algo;
int any = 0;
const char *(*get_info)( int, size_t*,byte**, int*, int*,
void (**)(void*),
void (**)(void*,byte*,size_t),
void (**)(void*),byte *(**)(void*));
if( !initialized ) {
cipher_modules_constructor();
initialized = 1;
}
algo = req_algo;
if( algo > 255 || !algo )
return 0; /* algorithm number too high (does not fit into out bitmap)*/
if( checked_all )
return 0; /* already called with -1 */
if( algo < 0 )
checked_all = 1;
else if( (checked_algos[algo/32] & (1 << (algo%32))) )
return 0; /* already checked and not found */
else
checked_algos[algo/32] |= (1 << (algo%32));
while( enum_gnupgext_digests( &context, &algo, &get_info ) ) {
if( req_algo != -1 && algo != req_algo )
continue;
for(r=digest_list; r; r = r->next )
if( r->algo == algo )
break;
if( r ) {
log_info("skipping digest %d: already loaded\n", algo );
continue;
}
r = new_list_item( algo, get_info );
if( ! r ) {
log_info("skipping digest %d: no name\n", algo );
continue;
}
/* put it into the list */
if( g10_opt_verbose > 1 )
log_info("loaded digest %d\n", algo);
r->next = digest_list;
digest_list = r;
any = 1;
if( req_algo != -1 )
break;
}
enum_gnupgext_digests( &context, NULL, NULL );
return any;
}
/****************
* Map a string to the digest algo
*/
int
gcry_md_map_name( const char *string )
{
struct md_digest_list_s *r;
do {
for(r = digest_list; r; r = r->next )
if( !stricmp( r->name, string ) )
return r->algo;
} while( !r && load_digest_module(-1) );
return 0;
}
/****************
* Map a digest algo to a string
*/
static const char *
digest_algo_to_string( int algo )
{
struct md_digest_list_s *r;
do {
for(r = digest_list; r; r = r->next )
if( r->algo == algo )
return r->name;
} while( !r && load_digest_module( algo ) );
return NULL;
}
/****************
* This function simply returns the name of the algorithm or some constant
* string when there is no algo. It will never return NULL.
* Use the macro gcry_md_test_algo() to check whether the algorithm
* is valid.
*/
const char *
gcry_md_algo_name( int algo )
{
const char *s = digest_algo_to_string( algo );
return s? s: "?";
}
static int
check_digest_algo( int algo )
{
struct md_digest_list_s *r;
do {
for(r = digest_list; r; r = r->next )
if( r->algo == algo )
return 0;
} while( !r && load_digest_module(algo) );
return GCRYERR_INV_MD_ALGO;
}
/****************
* Open a message digest handle for use with algorithm ALGO.
* More algorithms may be added by md_enable(). The initial algorithm
* may be 0.
*/
static GCRY_MD_HD
md_open( int algo, int secure )
{
GCRY_MD_HD hd;
struct gcry_md_context *ctx;
int bufsize = secure? 512 : 1024;
size_t n;
/* Allocate a memory area to hold the caller visible buffer with it's
* control information and the data required by this module. Set the
* context pointer at the beginning to this area.
* We have to use this strange scheme because we want to hide the
* internal data but have a variable sized buffer.
*
* +---+------+---........------+-------------+
* !ctx! bctl ! buffer ! private !
* +---+------+---........------+-------------+
* ! ^
* !---------------------------!
*
* We have to make sture that private is well aligned.
*/
n = sizeof( struct gcry_md_handle ) + bufsize;
n = ((n + sizeof(PROPERLY_ALIGNED_TYPE)-1)
/ sizeof(PROPERLY_ALIGNED_TYPE) ) * sizeof(PROPERLY_ALIGNED_TYPE);
/* allocate and set the Context pointer to the private data */
hd = secure ? g10_malloc_secure( n + sizeof( struct gcry_md_context ) )
: g10_malloc( n + sizeof( struct gcry_md_context ) );
if( !hd ) {
set_lasterr( GCRYERR_NO_MEM );
return NULL;
}
hd->ctx = ctx = (struct gcry_md_context*)( (char*)hd + n );
/* setup the globally visible data (bctl in the diagram)*/
hd->bufsize = n - sizeof( struct gcry_md_handle ) + 1;
hd->bufpos = 0;
/* initialize the private data */
memset( hd->ctx, 0, sizeof *hd->ctx );
ctx->magic = secure ? CTX_MAGIC_SECURE : CTX_MAGIC_NORMAL;
ctx->secure = secure;
fast_random_poll(); /* FIXME: should we really do that? */
if( algo && md_enable( hd, algo ) ) {
md_close( hd );
return NULL;
}
return hd;
}
GCRY_MD_HD
gcry_md_open( int algo, unsigned int flags )
{
GCRY_MD_HD hd;
/* fixme: check that algo is available and that only valid
* flag values are used */
hd = md_open( algo, (flags & GCRY_MD_FLAG_SECURE) );
return hd;
}
static int
md_enable( GCRY_MD_HD hd, int algo )
{
struct gcry_md_context *h = hd->ctx;
struct md_digest_list_s *r, *ac;
for( ac=h->list; ac; ac = ac->next )
if( ac->algo == algo )
return 0; /* already enabled */
/* find the algorithm */
do {
for(r = digest_list; r; r = r->next )
if( r->algo == algo )
break;
} while( !r && load_digest_module( algo ) );
if( !r ) {
log_debug("md_enable: algorithm %d not available\n", algo );
return set_lasterr( GCRYERR_INV_MD_ALGO );
}
/* and allocate a new list entry */
ac = h->secure? g10_malloc_secure( sizeof *ac + r->contextsize
- sizeof(r->context) )
: g10_malloc( sizeof *ac + r->contextsize
- sizeof(r->context) );
if( !ac )
return set_lasterr( GCRYERR_NO_MEM );
*ac = *r;
ac->next = h->list;
h->list = ac;
/* and init this instance */
(*ac->init)( &ac->context.c );
return 0;
}
int
gcry_md_enable( GCRY_MD_HD hd, int algo )
{
return md_enable( hd, algo );
}
static GCRY_MD_HD
md_copy( GCRY_MD_HD ahd )
{
struct gcry_md_context *a = ahd->ctx;
struct gcry_md_context *b;
GCRY_MD_HD bhd;
struct md_digest_list_s *ar, *br;
size_t n;
if( ahd->bufpos )
md_write( ahd, NULL, 0 );
n = (char*)ahd->ctx - (char*)ahd;
bhd = a->secure ? g10_malloc_secure( n + sizeof( struct gcry_md_context ) )
: g10_malloc( n + sizeof( struct gcry_md_context ) );
if( !bhd ) {
set_lasterr( GCRYERR_NO_MEM );
return NULL;
}
bhd->ctx = b = (struct gcry_md_context*)( (char*)bhd + n );
/* no need to copy the buffer due to the write above */
assert( ahd->bufsize == (n - sizeof( struct gcry_md_handle ) + 1) );
bhd->bufsize = ahd->bufsize;
bhd->bufpos = 0; assert( !ahd->bufpos );
memcpy( b, a, sizeof *a );
b->list = NULL;
b->debug = NULL;
/* and now copy the complete list of algorithms */
/* I know that the copied list is reversed, but that doesn't matter */
for( ar=a->list; ar; ar = ar->next ) {
br = a->secure ? g10_xmalloc_secure( sizeof *br + ar->contextsize
- sizeof(ar->context) )
: g10_xmalloc( sizeof *br + ar->contextsize
- sizeof(ar->context) );
memcpy( br, ar, sizeof(*br) + ar->contextsize
- sizeof(ar->context) );
br->next = b->list;
b->list = br;
}
if( a->debug )
md_start_debug( bhd, "unknown" );
return bhd;
}
GCRY_MD_HD
gcry_md_copy( GCRY_MD_HD hd )
{
return md_copy( hd );
}
/****************
* Reset all contexts and discard any buffered stuff. This may be used
* instead of a md_close(); md_open().
*/
void
gcry_md_reset( GCRY_MD_HD a )
{
struct md_digest_list_s *r;
a->bufpos = a->ctx->finalized = 0;
for( r=a->ctx->list; r; r = r->next ) {
memset( r->context.c, 0, r->contextsize );
(*r->init)( &r->context.c );
}
}
static void
md_close(GCRY_MD_HD a)
{
struct md_digest_list_s *r, *r2;
if( !a )
return;
if( a->ctx->debug )
md_stop_debug(a);
for(r=a->ctx->list; r; r = r2 ) {
r2 = r->next;
g10_free(r);
}
g10_free(a);
}
void
gcry_md_close( GCRY_MD_HD hd )
{
md_close( hd );
}
static void
md_write( GCRY_MD_HD a, byte *inbuf, size_t inlen)
{
struct md_digest_list_s *r;
if( a->ctx->debug ) {
if( a->bufpos && fwrite(a->buf, a->bufpos, 1, a->ctx->debug ) != 1 )
BUG();
if( inlen && fwrite(inbuf, inlen, 1, a->ctx->debug ) != 1 )
BUG();
}
for(r=a->ctx->list; r; r = r->next ) {
if( a->bufpos )
(*r->write)( &r->context.c, a->buf, a->bufpos );
(*r->write)( &r->context.c, inbuf, inlen );
}
a->bufpos = 0;
}
void
gcry_md_write( GCRY_MD_HD hd, const byte *inbuf, size_t inlen)
{
md_write( hd, (byte*)inbuf, inlen );
}
static void
md_final(GCRY_MD_HD a)
{
struct md_digest_list_s *r;
if( a->ctx->finalized )
return;
if( a->bufpos )
md_write( a, NULL, 0 );
for(r=a->ctx->list; r; r = r->next ) {
(*r->final)( &r->context.c );
}
a->ctx->finalized = 1;
}
int
gcry_md_ctl( GCRY_MD_HD hd, int cmd, byte *buffer, size_t buflen)
{
if( cmd == GCRYCTL_FINALIZE )
md_final( hd );
else
return GCRYERR_INV_OP;
return 0;
}
/****************
* if ALGO is null get the digest for the used algo (which should be only one)
*/
static byte *
md_read( GCRY_MD_HD a, int algo )
{
struct md_digest_list_s *r;
if( !algo ) { /* return the first algorithm */
if( (r=a->ctx->list) ) {
if( r->next )
log_debug("more than algorithm in md_read(0)\n");
return (*r->read)( &r->context.c );
}
}
else {
for(r=a->ctx->list; r; r = r->next )
if( r->algo == algo )
return (*r->read)( &r->context.c );
}
BUG();
return NULL;
}
/****************
* Read out the complete digest, this function implictly finalizes
* the hash.
*/
byte *
gcry_md_read( GCRY_MD_HD hd, int algo )
{
gcry_md_ctl( hd, GCRYCTL_FINALIZE, NULL, 0 );
return md_read( hd, algo);
}
/****************
* This function combines md_final and md_read but keeps the context
* intact. This function can be used to calculate intermediate
* digests. The digest is copied into buffer and the digestlength is
* returned. If buffer is NULL only the needed size for buffer is returned.
* buflen gives the max size of buffer. If the buffer is too shourt to
* hold the complete digest, the buffer is filled with as many bytes are
* possible and this value is returned.
*/
#if 0
static int
md_digest( GCRY_MD_HD a, int algo, byte *buffer, int buflen )
{
struct md_digest_list_s *r = NULL;
char *context;
char *digest;
if( a->bufpos )
md_write( a, NULL, 0 );
if( !algo ) { /* return digest for the first algorithm */
if( (r=a->ctx->list) && r->next )
log_debug("more than algorithm in md_digest(0)\n");
}
else {
for(r=a->ctx->list; r; r = r->next )
if( r->algo == algo )
break;
}
if( !r )
BUG();
if( !buffer )
return r->mdlen;
/* I don't want to change the interface, so I simply work on a copy
* of the context (extra overhead - should be fixed)*/
context = a->ctx->secure ? g10_xmalloc_secure( r->contextsize )
: g10_xmalloc( r->contextsize );
memcpy( context, r->context.c, r->contextsize );
(*r->final)( context );
digest = (*r->read)( context );
if( buflen > r->mdlen )
buflen = r->mdlen;
memcpy( buffer, digest, buflen );
g10_free(context);
return buflen;
}
#endif
/****************
* Read out an intermediate digest.
*/
int
gcry_md_get( GCRY_MD_HD hd, int algo, byte *buffer, int buflen )
{
/*md_digest ... */
return GCRYERR_INTERNAL;
}
/****************
* Shortcut function to hash a buffer with a given algo. The only supported
* algorithm is RIPE-MD. The supplied digest buffer must be large enough
* to store the resulting hash. No error is returned, the function will
* abort on an invalite algo. DISABLED_ALGOS are ignored here.
*/
void
gcry_md_hash_buffer( int algo, char *digest, const char *buffer, size_t length)
{
if( algo == GCRY_MD_RMD160 )
rmd160_hash_buffer( digest, buffer, length );
else
BUG();
}
static int
md_get_algo( GCRY_MD_HD a )
{
struct md_digest_list_s *r;
if( (r=a->ctx->list) ) {
if( r->next )
log_error("WARNING: more than algorithm in md_get_algo()\n");
return r->algo;
}
return 0;
}
int
gcry_md_get_algo( GCRY_MD_HD hd )
{
return md_get_algo( hd ); /* fixme: we need error handling */
}
/****************
* Return the length of the digest
*/
static int
md_digest_length( int algo )
{
struct md_digest_list_s *r;
do {
for(r = digest_list; r; r = r->next ) {
if( r->algo == algo )
return r->mdlen;
}
} while( !r && load_digest_module( algo ) );
return 0;
}
/****************
* Return the length of the digest in bytes.
* This function will return 0 in case of errors.
*/
unsigned int
gcry_md_get_algo_dlen( int algo )
{
/* we do some very quick checks here */
switch( algo )
{
case GCRY_MD_MD5: return 16;
case GCRY_MD_SHA1:
case GCRY_MD_RMD160: return 20;
default: {
int len = md_digest_length( algo );
if( !len )
set_lasterr( GCRYERR_INV_MD_ALGO );
return 0;
}
}
}
/* Hmmm: add a mode to enumerate the OIDs
* to make g10/sig-check.c more portable */
static const byte *
md_asn_oid( int algo, size_t *asnlen, size_t *mdlen )
{
struct md_digest_list_s *r;
do {
for(r = digest_list; r; r = r->next ) {
if( r->algo == algo ) {
if( asnlen )
*asnlen = r->asnlen;
if( mdlen )
*mdlen = r->mdlen;
return r->asnoid;
}
}
} while( !r && load_digest_module( algo ) );
log_bug("no asn for md algo %d\n", algo);
return NULL;
}
/****************
* Return information about the given cipher algorithm
* WHAT select the kind of information returned:
* GCRYCTL_TEST_ALGO:
* Returns 0 when the specified algorithm is available for use.
* buffer and nbytes must be zero.
* GCRYCTL_GET_ASNOID:
* Return the ASNOID of the algorithm in buffer. if buffer is NULL, only
* the required length is returned.
*
* On error the value -1 is returned and the error reason may be
* retrieved by gcry_errno().
* Note: Because this function is in most caes used to return an
* integer value, we can make it easier for the caller to just look at
* the return value. The caller will in all cases consult the value
* and thereby detecting whether a error occured or not (i.e. while checking
* the block size)
*/
int
gcry_md_algo_info( int algo, int what, void *buffer, size_t *nbytes)
{
switch( what ) {
case GCRYCTL_TEST_ALGO:
if( buffer || nbytes ) {
set_lasterr( GCRYERR_INV_ARG );
return -1;
}
if( check_digest_algo( algo ) ) {
set_lasterr( GCRYERR_INV_MD_ALGO );
return -1;
}
break;
case GCRYCTL_GET_ASNOID: {
size_t asnlen;
const char *asn = md_asn_oid( algo, &asnlen, NULL );
if( buffer && *nbytes >= asnlen ) {
memcpy( buffer, asn, asnlen );
*nbytes = asnlen;
return 0;
}
if( !buffer && nbytes ) {
*nbytes = asnlen;
return 0;
}
set_lasterr( buffer ? GCRYERR_TOO_SHORT : GCRYERR_INV_ARG );
return -1;
}
break;
default:
set_lasterr( GCRYERR_INV_OP );
return -1;
}
return 0;
}
void
md_start_debug( GCRY_MD_HD md, const char *suffix )
{
static int idx=0;
char buf[25];
if( md->ctx->debug ) {
log_debug("Oops: md debug already started\n");
return;
}
idx++;
sprintf(buf, "dbgmd-%05d.%.10s", idx, suffix );
md->ctx->debug = fopen(buf, "w");
if( !md->ctx->debug )
log_debug("md debug: can't open %s\n", buf );
}
void
md_stop_debug( GCRY_MD_HD md )
{
if( md->ctx->debug ) {
if( md->bufpos )
md_write( md, NULL, 0 );
fclose(md->ctx->debug);
md->ctx->debug = NULL;
}
#ifdef HAVE_U64_TYPEDEF
{ /* a kludge to pull in the __muldi3 for Solaris */
volatile u32 a = (u32)(ulong)md;
volatile u64 b = 42;
volatile u64 c;
c = a * b;
}
#endif
}
/****************
* Return information about the digest handle.
* GCRYCTL_IS_SECURE:
* Returns 1 when the handle works on secured memory
* otherwise 0 is returned. There is no error return.
*/
int
gcry_md_info( GCRY_MD_HD h, int cmd, void *buffer, size_t *nbytes)
{
switch( cmd ) {
case GCRYCTL_IS_SECURE:
return h->ctx->secure;
default:
set_lasterr( GCRYERR_INV_OP );
return -1;
}
return 0;
}
|