source: libabac/abac_openssl.c @ 7764378

abac0-leak
Last change on this file since 7764378 was f2622ee, checked in by Mei-Hui Su <mei@…>, 11 years ago

1) ran with valgrind and did some leak patching

  • Property mode set to 100644
File size: 17.1 KB
RevLine 
[461541a]1
2/* abac_openssl.c */
3
4#define _GNU_SOURCE
5#include <stdio.h>
6#include <stdlib.h>
7#include <assert.h>
8#include <ctype.h>
9#include <unistd.h>
10
11#include <fcntl.h>
12#include <sys/types.h>
13#include <sys/stat.h>
14#include <sys/mman.h>
15#include <time.h>
16
17#include <stdbool.h>
18
19#include <openssl/conf.h>
20#include <openssl/x509.h>
21#include <openssl/x509v3.h>
22#include <openssl/x509_vfy.h>
23
24#include <openssl/rsa.h>
25#include <openssl/evp.h>
26#include <openssl/err.h>
27#include <openssl/pem.h>
28#include <openssl/ssl.h>
[bfaec48]29#include <openssl/sha.h>
[461541a]30#include <openssl/rand.h>
31
32
33#ifdef HAVE_READPASSPHRASE
34# include <readpassphrase.h>
35#else
36# include "compat/readpassphrase.h"
37#endif
38
39static int debug=0;
40
41int _potato_cb(char *buf, int sz, int rwflag, void *u);
42
43/***********************************************************************/
44int init_openssl() {
[b73c5d05]45    if(debug) ERR_load_crypto_strings();
[461541a]46    OpenSSL_add_all_algorithms();
47    return 0;
48}
49
50int deinit_openssl() {
51    CRYPTO_cleanup_all_ex_data();
52    return 0;
53}
54
55/* int RAND_bytes(unsigned char *buf, int num); */
56unsigned char *abac_generate_serial() {
57    unsigned char *serial=(unsigned char *) malloc(sizeof(unsigned char)*8);
58
59    memset(serial, '\0', 8);
60
61    if(!RAND_bytes(serial,8)) {
62        fprintf(stderr,"RAT, RAN^D out of seeds!!!\n");
63        assert(0);
64    }
65    // zap leading 0's
66    while (serial[0] == 0)
67        RAND_bytes(&serial[0],1);
68
69    RAND_cleanup();
70    return serial;
71}
72
73
74static BIGNUM *_make_bn_from_string(unsigned char *str)
75{
76    assert(str);
77    BIGNUM *tmp;
78    tmp=BN_bin2bn(str,8,NULL);
79/* BN_print_fp(stderr,tmp); */
80    int n=BN_num_bytes(tmp);
81    if(n) return tmp;
82        return NULL;
83}
84
85unsigned char *_encode_m64(unsigned char *orig_ptr, int orig_len)
86{
87    BIO *mbio,*b64bio,*bio;
88
89    unsigned char *m64_ptr=NULL;
90    int m64_len=0;
91
92    unsigned char *ptr=NULL;
93
94    if(orig_len==0) return NULL;
95
96    /*bio pointing at b64->mem, the base64 bio encodes on
97      write and decodes on read */
98    mbio=BIO_new(BIO_s_mem());
99    b64bio=BIO_new(BIO_f_base64());
100    bio=BIO_push(b64bio,mbio);
101
102    BIO_write(bio,orig_ptr,orig_len);
103
104    /* We need to 'flush' things to push out the encoding of the
105    * last few bytes.  There is special encoding if it is not a
106    * multiple of 3
107    */
108    BIO_flush(bio);
109
110    /* pointer to the data and the number of elements. */
111    m64_len=(int)BIO_ctrl(mbio,BIO_CTRL_INFO,0,ptr);
112
113    if(m64_len!=0) {
114       m64_ptr=malloc(m64_len+1);
115       if(m64_ptr) {
116           strcpy((char *)m64_ptr, (char *)ptr);
117           } else { 
118               fprintf(stderr,"ERROR: malloc failed\n");
119       }
120    }
121
122    /* This call will walk the chain freeing all the BIOs */
123    BIO_free_all(bio);
124    return m64_ptr;
125}
126
127unsigned char *_decode_m64(unsigned char *m64_ptr, int m64_len)
128{
129    unsigned char *orig_ptr=NULL;
130    int orig_len=0;
131
132    BIO *b64bio, *mbio, *bio;
133    char *ptr=NULL;
134
135    if(m64_len==0) return NULL;
136
137    ptr = (char *)malloc(sizeof(char)*m64_len);
138    memset(ptr, '\0', m64_len);
139
140    b64bio = BIO_new(BIO_f_base64());
141    mbio = BIO_new_mem_buf(m64_ptr, m64_len);
142    bio = BIO_push(b64bio, mbio);
143
144    orig_len=BIO_read(bio, ptr, m64_len);
145   
146    if(orig_len) {
147        orig_ptr=malloc(orig_len+1);
148        if(orig_ptr)
149            strcpy((char *)orig_ptr, ptr);
150            else fprintf(stderr,"ERROR: malloc failed..\n");
151    }
152
153    BIO_free_all(bio);
154    return orig_ptr;
155}
156
[9ac7fb4]157/*** not used
[461541a]158static char *_read_blob_from_file(char *fname, int *len)
159{
160    struct stat sb;
161    char *dptr=NULL;
162
163    int fd = open(fname, O_RDONLY);
164    if (fd == -1) { return NULL; }
165    if(stat(fname, &sb) == -1) {
166        close(fd);
167        return NULL;
168    }
169    dptr= (char *)mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
170    close(fd);
171
172    if(dptr == MAP_FAILED) {
173        return NULL;
174    }
175    *len=sb.st_size;
176    return dptr;
177}
[9ac7fb4]178***/
[461541a]179
180/* Read ID in PEM */
181X509 *abac_load_id_from_fp(FILE *fp)
182{
183    X509 *cert=PEM_read_X509(fp,NULL,NULL,NULL);
184
185    if (cert == NULL) {
[b73c5d05]186        if(debug) ERR_print_errors_fp (stderr);
[461541a]187        return NULL;
188    }
189    return cert;
190}
191
192X509 *abac_load_id_from_chunk(unsigned char *chunk_ptr, int chunk_len)
193{
194    X509 *n509=NULL;
195    BIO *mbio=BIO_new(BIO_s_mem());
196
197    BIO_write(mbio,chunk_ptr,chunk_len);
198    BIO_flush(mbio);
199
[f2622ee]200    if( !PEM_read_bio_X509(mbio,&n509,0,NULL)) {
201        if(debug) ERR_print_errors_fp (stderr);
202        return NULL;
203    } 
[461541a]204
205    BIO_free_all(mbio);
206
207    return n509;
208}
209
210int abac_write_id_to_fp(X509 *cert, FILE *fp)
211{
212    assert(cert);
213
[7e3f5e2]214    if(!PEM_write_X509(fp,cert)) {
[b73c5d05]215        if(debug) ERR_print_errors_fp (stderr);
[7e3f5e2]216        return 1;
[461541a]217    }
218    return 0;
219}
220
221/* make stringfy a private key PEM struct */
222unsigned char *abac_string_privkey(EVP_PKEY *key)
223{
224    unsigned char *ptr=NULL;
225    unsigned char *tmp=NULL;
226
227    assert(key);
228
229    BIO *mbio=BIO_new(BIO_s_mem());
230    /* PEM_write_PrivateKey(fp,key,NULL,NULL,0,_potato_cb, "privateKey to file"); */
231    PEM_write_bio_PrivateKey(mbio,key,NULL,NULL,0,_potato_cb,"stringify privateKey");
232    BIO_flush(mbio);
233    int len=(int)BIO_ctrl(mbio,BIO_CTRL_INFO,0,tmp);
234
235    if(debug) fprintf(stderr,"CHUNKING PrivateKey... %d\n",len);
236
237    if(len) {
238        ptr=(unsigned char *)malloc(sizeof(unsigned char *)*(len+1));
239        int ret=BIO_read(mbio, (void *)ptr, len);
240        if(ret==0)
241            fprintf(stderr," abac_string_privkey failed!!\n");
242        ptr[len]='\0';
243    }
244    BIO_free_all(mbio);
245    return ptr;
246}
247
248/* make stringfy a x509 PEM struct */
249unsigned char *abac_string_cert(X509 *cert) {
250    unsigned char *ptr=NULL;
251    unsigned char *tmp=NULL;
252
253    assert(cert);
254
255    BIO *mbio=BIO_new(BIO_s_mem());
256    PEM_write_bio_X509(mbio,cert);
257    BIO_flush(mbio);
258    int len=(int)BIO_ctrl(mbio,BIO_CTRL_INFO,0,tmp);
259    if(debug) fprintf(stderr,"CHUNKING X509... %d\n",len);
260
261    if(len) {
262        ptr=(unsigned char *)malloc(sizeof(unsigned char *)*(len+1));
263        int ret=BIO_read(mbio, (void *)ptr, len);
264        if(ret==0)
265            fprintf(stderr," abac_string_cert failed!!\n");
266        ptr[len]='\0';
267    }
268   
269    BIO_free_all(mbio);
270    return ptr;
271}
272
273
[9ac7fb4]274/* not used, sign data with privkey 
[461541a]275static int _sign_with_privkey(EVP_PKEY *privkey, char *data,
276unsigned char* signed_buf)
277{
278  int err;
279  unsigned int signed_len;
280  EVP_MD_CTX     md_ctx;
281
282  EVP_SignInit   (&md_ctx, EVP_md5());
283  EVP_SignUpdate (&md_ctx, data, strlen(data));
284  signed_len = sizeof(signed_buf);
285  err = EVP_SignFinal (&md_ctx,
286                       signed_buf,
287                       &signed_len,
288                       privkey);
289  if (err != 1) {
[b73c5d05]290      if(debug) ERR_print_errors_fp (stderr);
[461541a]291      return 1;
292  }
293  return 0;
294}
[9ac7fb4]295**/
[461541a]296
[9ac7fb4]297/* nost used, verify the signature..
[461541a]298static int _verify_with_pubkey(EVP_PKEY *pubkey, char *data,
299unsigned char* signed_buf )
300{
301  int err;
302  int signed_len;
303  EVP_MD_CTX     md_ctx;
304
305  EVP_VerifyInit   (&md_ctx, EVP_sha1());
306  EVP_VerifyUpdate (&md_ctx, data, strlen((char*)data));
307  signed_len=sizeof(signed_buf);
308  err = EVP_VerifyFinal (&md_ctx,
309                         signed_buf,
310                         signed_len,
311                         pubkey);
312
313  if (err != 1) {
[b73c5d05]314        if(debug) ERR_print_errors_fp (stderr);
[461541a]315        return 1;
316  }
317  fprintf(stderr, "Signature Verified Ok.\n");
318  return 0;
319}
[9ac7fb4]320***/
[461541a]321
322
323#define PWLEN 128
324/* EVP_PKEY *PEM_read_PrivateKey(FILE *,EVP_PKEY **,pem_cb *,void *) */
325int _potato_cb(char *buf, int sz, int rwflag, void *u)
326{
327   int len;
328   char *prompt=NULL;
[3c30b59]329   int rc;
[461541a]330   if(u)
[3c30b59]331       rc=asprintf(&prompt,"Enter passphrase for %s:", (char *)u);
332       else rc=asprintf(&prompt,"Enter passphrase :");
[461541a]333   char *secret = malloc(PWLEN);
334   memset(secret, '\0', PWLEN);
335   if(!secret) {
336        perror("malloc()");
337        free(prompt);
338        return 0;
339   }
340   if (readpassphrase( prompt, secret, PWLEN, RPP_ECHO_OFF) == NULL) {
341       perror("readpassphrase()");
342       memset(secret, '\0', PWLEN);
343       len=0;
344       } else {
345           len=strlen(secret);
346           memcpy(buf, secret, len);
347           memset(secret, '\0', len);
348   }
349   free(secret);
350   free(prompt);
351   return len;
352}
353
[a02c849]354EVP_PKEY *abac_load_privkey_from_chunk(unsigned char *chunk_ptr, int chunk_len)
355{
356    EVP_PKEY *nkey=NULL;
357    BIO *mbio=BIO_new(BIO_s_mem());
358
359    BIO_write(mbio,chunk_ptr,chunk_len);
360    BIO_flush(mbio);
361
362    PEM_read_bio_PrivateKey(mbio,&nkey,NULL,NULL);
363
364    BIO_free_all(mbio);
365
366    if (nkey == NULL) {
[b73c5d05]367        if(debug) ERR_print_errors_fp (stderr);
[a02c849]368        return NULL;
369    }
370    return nkey;
371}
372
[461541a]373EVP_PKEY *abac_load_privkey_from_fp(FILE *fp)
374{
375    assert(fp);
376
377    EVP_PKEY *privkey = PEM_read_PrivateKey(fp, NULL, _potato_cb, "privateKey from file");
378
379    if (privkey == NULL) {
[b73c5d05]380        if(debug) ERR_print_errors_fp (stderr);
[461541a]381        return NULL;
382    }
383    return privkey;
384}
385
386/* not adding passphrase */
387int abac_write_privkey_to_fp(EVP_PKEY *key, FILE *fp) {
388    assert(key);
389
[7e3f5e2]390    if(!PEM_write_PrivateKey(fp,key,NULL,NULL,0,NULL, NULL)) {
[b73c5d05]391        if(debug) ERR_print_errors_fp (stderr);
[7e3f5e2]392        return 1;
393    }
[461541a]394    return 0;
395}
396
397/* adding passphrase */
398int abac_write_encrypt_privkey_to_fp(EVP_PKEY *key, FILE *fp) {
399    assert(key);
400
[7e3f5e2]401    if(!PEM_write_PrivateKey(fp,key,NULL,NULL,0,_potato_cb, "privateKey to file")) {
[b73c5d05]402        if(debug) ERR_print_errors_fp (stderr);
[7e3f5e2]403        return 1;
404    }
[461541a]405    return 0;
406}
407
408EVP_PKEY *extract_pubkey_from_cert(X509 *cert)
409{
410    EVP_PKEY *pubkey=X509_get_pubkey(cert);
411    return pubkey;
412}
413
414
[9ac7fb4]415/** not used,
[461541a]416static void _callback(int p, int n, void *arg)
417{
418    char c='B';
419
420    if (p == 0) c='.';
421    if (p == 1) c='+';
422    if (p == 2) c='*';
423    if (p == 3) c='\n';
424    fputc(c,stderr);
425}
[9ac7fb4]426***/
[461541a]427
428/*
429RSA *RSA_generate_key(int num, unsigned long e,
430   void (*callback)(int,int,void *), void *cb_arg);
431The exponent is an odd number, typically 3, 17 or 65537
432*/
433EVP_PKEY* abac_generate_key()
434{
435    EVP_PKEY *pk=NULL;
436    int keysize=2048;
437
438    if((pk=EVP_PKEY_new()) == NULL){
[b73c5d05]439        if(debug) ERR_print_errors_fp (stderr);
[461541a]440        return NULL;
441    }
442
443//    RSA *rsa=RSA_generate_key(keysize,RSA_F4,_callback,NULL);
444    RSA *rsa=RSA_generate_key(keysize,RSA_F4,NULL,NULL); 
445    if (!EVP_PKEY_assign_RSA(pk,rsa)) {
[b73c5d05]446        if(debug) ERR_print_errors_fp (stderr);
[461541a]447        return NULL;
448    }
449    rsa=NULL;
450
451    return pk;
452}
453
454/* Add extension using V3 code: we can set the config file as NULL
455 * because we wont reference any other sections.
456 */
457static int _add_ext(X509 *cert, int nid, char *value)
458{
459    X509_EXTENSION *ex;
460    X509V3_CTX ctx;
461    /* This sets the 'context' of the extensions. */
462    /* No configuration database */
463    X509V3_set_ctx_nodb(&ctx);
464    /* Issuer and subject certs: both the target since it is self signed,
465     * no request and no CRL
466     */
467    X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
468    ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
469    if (!ex)
470        return 0;
471
472    X509_add_ext(cert,ex,-1);
473    X509_EXTENSION_free(ex);
474    return 1;
475}
476
477
478/**
479 * Generate ID certificate.
480 *
481 * validity is measured in seconds (as of 0.2.0)
482 */
483X509 *abac_generate_cert(EVP_PKEY *pkey, char *cn, long validity) {
484
[9e063cb]485    /* must have a privkey before generating an ID cert */
[461541a]486    assert(pkey);
487    X509 *cert=NULL;
488
489    if((cert=X509_new()) == NULL)
490            goto error;
491
492    unsigned char *serial=abac_generate_serial();
493
494    if(validity == 0) validity=(long)(60*60*24*(365));
495
496    X509_set_version(cert,2);
497
498    BIGNUM *bn=_make_bn_from_string(serial);
499    BN_to_ASN1_INTEGER(bn, X509_get_serialNumber(cert));
500    /* this is prone to problem with very big days on 32 bit machines,
501       In newer openssl, can migrate to X509_time_adj_ex */ 
502    X509_gmtime_adj(X509_get_notBefore(cert),0);
503    X509_gmtime_adj(X509_get_notAfter(cert),validity);
504    X509_set_pubkey(cert,pkey);
505
506    X509_NAME *name=X509_get_subject_name(cert);
507
508    if(!name) goto error;
509
510    /* This function creates and adds the entry, working out the
511     * correct string type and performing checks on its length.
512     */
513    if(!(X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC, (unsigned char *)cn, -1, -1, 0)))
514        goto error; // fail to add cn to cert
515
516    /* Self signed, set the issuer name to be the same as the subject. */
517    if(!(X509_set_issuer_name(cert,name)))
518        goto error; // fail to set issuer name on cert
519
520    /* Add various extensions: standard extensions */
521    if(!(_add_ext(cert, NID_basic_constraints, "critical,CA:TRUE")))
522        goto error; // fail to set basic constraint
523    if(!(_add_ext(cert, NID_key_usage, "critical,keyCertSign,cRLSign")))
524        goto error; // fail to set key usage
525    if(!(_add_ext(cert, NID_subject_key_identifier, "hash")))
526        goto error; // fail to set subject key identifier
527    if(!(_add_ext(cert, NID_authority_key_identifier, "keyid:always")))
528        goto error; // fail to set authority key identifier (self-signing)
529
530    /* make sure it is signed */
531    if (!X509_sign(cert,pkey,EVP_sha1()))
532        goto error;
533
534    return cert;
535
536error:
537    if(cert) X509_free(cert);
538    if(serial) free(serial);
539    if(bn) BN_free(bn);
540    return NULL;
541}
542
[9ac7fb4]543/*** not used,
[461541a]544static char *_time_in_string(ASN1_TIME *tm)
545{
546    char *ptr=NULL;
547    BIO *mbio=BIO_new(BIO_s_mem());
548    ASN1_TIME_print(mbio, tm);
549    BIO_flush(mbio);
550    int len=BIO_number_written(mbio);
551    ptr=(char *) malloc(sizeof(char *)*(len+1));
552    int ret=BIO_read(mbio, (void *)ptr, len);
553
554    BIO_free_all(mbio);
555    if(ret)
556        return ptr;
557        else return NULL;
558}
[9ac7fb4]559***/
[461541a]560
561
562/* atime->data, YYmmddHHMMSS or YYYYmmddHHMMSSZZ
563 *  V_ASN1_UTCTIME, V_ASN1_GENERALIZEDTIME
564 */
565static int _convert_time(struct tm *ttime, ASN1_TIME *atime) {
566    assert(atime); assert(atime->data);
567
568    int type=atime->type;
569    int len=strlen((char *)atime->data);
570    if(len==0) return 0;
571
572    char *astring=strndup((char *)atime->data,len);
573
574    /* setting ttime structure */
575    if (type == V_ASN1_UTCTIME) {
576           strptime(astring, "%y%m%d%H%M%S", ttime);
577        } else {
578        if (type == V_ASN1_GENERALIZEDTIME)
579           strptime(astring, "%Y%m%d%H%M%S", ttime);
580           else fprintf(stderr,"ERROR,.. unknown type in ASN1_TIME struct\n");
581    }
582
583    if(debug) {
584        char *tstring=asctime(ttime);
585        fprintf(stderr,"PPP final time string is %s\n",tstring);
586    }
587    return 1;
588}
589
590/* check whether the cert is still valid or not and also extract what its
[9e063cb]591   not_before and not_after field, 0 is okay, 1 is not */
[461541a]592int abac_check_validity(X509 *cert, struct tm *not_before, struct tm *not_after) {
593    assert(cert);
594
595    int valid=0;
596    int ret=0;
597    memset(not_before, 0, sizeof(struct tm));
598    memset(not_after, 0, sizeof(struct tm));
599    ASN1_TIME *notAfter= X509_get_notAfter(cert);
600    ASN1_TIME *notBefore=X509_get_notBefore(cert);
601
602    ret=_convert_time(not_before, notBefore);
[9e063cb]603    if(ret==0) return 1;
[461541a]604    ret=_convert_time(not_after, notAfter);
[9e063cb]605    if(ret==0) return 1;
[461541a]606
607    if((X509_cmp_current_time(notBefore) >=0) ||
608                  (X509_cmp_current_time(notAfter) <=0) )
609      valid=1;
610
611    if(valid) return 0;
612       else return 1;
613}
614
615/* check if cert is still valid at current time, 1 for yes, 0 for no*/
616int abac_still_valid(X509 *cert)
617{
618    ASN1_TIME *notAfter= X509_get_notAfter(cert);
619    ASN1_TIME *notBefore=X509_get_notBefore(cert);
620    if(0) {
621        fprintf(stderr,"((X509_cmp_current_time(notBefore) is %d\n", 
622                                  X509_cmp_current_time(notBefore));
623        fprintf(stderr,"((X509_cmp_current_time(notAfter) is %d\n", 
624                                  X509_cmp_current_time(notAfter));
625    }
626    if((X509_cmp_current_time(notBefore) >=0) ||
627                   (X509_cmp_current_time(notAfter) <=0) )
628      return 0;
629    return 1;
630}
631
632char *abac_get_cn(X509 *cert)
633{
634   X509_NAME *nptr=X509_get_subject_name (cert);
635   int pos=X509_NAME_get_index_by_NID(nptr, NID_commonName,-1);
636   X509_NAME_ENTRY *ent=X509_NAME_get_entry(nptr,pos); 
637   ASN1_STRING *adata=X509_NAME_ENTRY_get_data(ent);
638   unsigned char *val=ASN1_STRING_data(adata);
[1bf0f03]639   if(debug) fprintf(stderr," cn:(%zu)(%s)\n", strlen((char *)val), val);
[461541a]640   return (char *) val;
641}
642
643char *abac_get_serial(X509 *cert)
644{
645   char *ret=NULL;
646   ASN1_INTEGER *num=X509_get_serialNumber(cert);
647   BIGNUM *bnser=ASN1_INTEGER_to_BN(num,NULL);
648   int n=BN_num_bytes(bnser);
649   unsigned char buf[n];
650   int b=BN_bn2bin(bnser,buf);
651   if(debug) { 
652       fprintf(stderr," extract serial number:->(%d)",b);
653       BN_print_fp(stderr,bnser);
654   } 
655   if(n)
656       ret=strndup((char *)buf,n);
657   return ret;
658}
659
660char *abac_get_subject(X509 *cert) 
661{
662   char *ptr=X509_NAME_oneline(X509_get_subject_name(cert),0,0);
663   return ptr;
664}
665
666char *abac_get_issuer(X509 *cert) 
667{   
668   char *ptr=X509_NAME_oneline(X509_get_issuer_name(cert),0,0);
669   return ptr;
670}
671
[bfaec48]672//  success: malloc'd calculated SHA1 of the key (as per RFC3280)
[461541a]673//  fail: NULL
674unsigned char *abac_get_keyid(X509 *cert)
675{
[bfaec48]676    char digest[SHA_DIGEST_LENGTH]; /* SSL computed key digest */
677    /* ASCII (UTF-8 compatible) text for the digest */
678    unsigned char *sha=(unsigned char *) malloc(2*SHA_DIGEST_LENGTH+1);
679    int i;  /* Scratch */
680
681    if ( !sha) return NULL;
682
683    if ( !X509_pubkey_digest(cert, EVP_sha1(), digest, NULL)) {
684        free(sha);
685        return NULL;
[461541a]686    }
687
[bfaec48]688    /* Translate to ASCII */
689    for ( i = 0; i < SHA_DIGEST_LENGTH; i++)
690        snprintf((char *) sha+2*i, 3, "%02x", digest[i] & 0xff);
691    sha[2*SHA_DIGEST_LENGTH] = '\0';
692
[461541a]693    if(debug) fprintf(stderr,"SHA1 -> %s\n",sha);
[bfaec48]694    return sha;
[461541a]695}
Note: See TracBrowser for help on using the repository browser.