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