On Wed, Aug 24, 2022 at 11:35:01PM +0000, Job Snijders wrote:
> Hi all,
> 
> Scratching an itch: When debugging RPKI things, I've grown tired of
> typing stuff like the below 2 commands to get to the CMS encapsulated
> DER encoded EE certificate in RPKI Signed Objects.
> 
>     $ openssl cms -verify -noverify -inform DER -signer signer.pem \
>         -in OOFPkv3HzPv8GCNhUjrifWl-lS8.mft > /dev/zero
>     $ openssl x509 -in signer.pem -text
> 
> Life is too short to type that many letters - instead, I'd rather:
> 
>     $ rpki-client -p OOFPkv3HzPv8GCNhUjrifWl-lS8.mft
> 
> For completeness' sake, also print all other (not-CMS encapsulated)
> file types such as CA certs, BGPsec Router Keys, and CRLs.

Kind of makes sense to me. Instead of adding another mode, could this be
done as a -p flag in file mode (or even be hidden behind the -v flag)?
Or is this too ugly due to repeated info?

Some comments inline.

> 
> OK?
> 
> Kind regards,
> 
> Job
> 
> Index: extern.h
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
> retrieving revision 1.150
> diff -u -p -r1.150 extern.h
> --- extern.h  19 Aug 2022 12:45:53 -0000      1.150
> +++ extern.h  24 Aug 2022 23:33:24 -0000
> @@ -664,6 +664,7 @@ void               mft_print(const X509 *, const str
>  void          roa_print(const X509 *, const struct roa *);
>  void          gbr_print(const X509 *, const struct gbr *);
>  void          rsc_print(const X509 *, const struct rsc *);
> +int           print_pem(char *);

This should probably be pem_print() to match all the other print functions.
Any reason it can't take a const char *?

>  
>  /* Output! */
>  
> Index: main.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v
> retrieving revision 1.209
> diff -u -p -r1.209 main.c
> --- main.c    4 Aug 2022 13:44:07 -0000       1.209
> +++ main.c    24 Aug 2022 23:33:25 -0000
> @@ -64,6 +64,7 @@ const char  *bird_tablename = "ROAS";
>  int  verbose;
>  int  noop;
>  int  filemode;
> +int  pemmode;
>  int  rrdpon = 1;
>  int  repo_timeout;
>  
> @@ -819,7 +820,7 @@ main(int argc, char *argv[])
>           "proc exec unveil", NULL) == -1)
>               err(1, "pledge");
>  
> -     while ((c = getopt(argc, argv, "b:Bcd:e:fjnorRs:S:t:T:vV")) != -1)
> +     while ((c = getopt(argc, argv, "b:Bcd:e:fjnoprRs:S:t:T:vV")) != -1)
>               switch (c) {
>               case 'b':
>                       bind_addr = optarg;
> @@ -849,6 +850,9 @@ main(int argc, char *argv[])
>               case 'o':
>                       outformats |= FORMAT_OPENBGPD;
>                       break;
> +             case 'p':
> +                     pemmode = 1;
> +                     break;
>               case 'R':
>                       rrdpon = 0;
>                       break;
> @@ -888,6 +892,17 @@ main(int argc, char *argv[])
>       argv += optind;
>       argc -= optind;
>  
> +     if (pemmode) {
> +             if (pledge("stdio rpath", NULL) == -1)
> +                     err(1, "pledge");
> +
> +             if (argc > 1)
> +                     goto usage;

This should be argc > 0 to match your synopsis.

Or should this behave like file mode and accept an arbitrary number of
file arguments? For example you could drop the argc check and do:

                for (i = 0; i < argc; i++) {
                        if ((rc = print_pem(argv[i])) != 0)
                                return rc
                }

> +
> +             rc = print_pem(argv[0]);
> +             return rc;
> +     }
> +
>       if (!filemode) {
>               if (argc == 1)
>                       outputdir = argv[0];
> @@ -1278,6 +1293,7 @@ usage:
>           " [-e rsync_prog]\n"
>           "                   [-S skiplist] [-s timeout] [-T table] [-t tal]"
>           " [outputdir]\n"
> -         "       rpki-client [-Vv] [-d cachedir] [-t tal] -f file ...\n");
> +         "       rpki-client [-Vv] [-d cachedir] [-t tal] -f file ...\n"
> +         "       rpki-client -p file\n");
>       return 1;
>  }
> Index: print.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/print.c,v
> retrieving revision 1.14
> diff -u -p -r1.14 print.c
> --- print.c   14 Jul 2022 13:24:56 -0000      1.14
> +++ print.c   24 Aug 2022 23:33:25 -0000
> @@ -1,5 +1,6 @@
>  /*   $OpenBSD: print.c,v 1.14 2022/07/14 13:24:56 job Exp $ */
>  /*
> + * Copyright (c) 2022 Job Snijders <[email protected]>
>   * Copyright (c) 2021 Claudio Jeker <[email protected]>
>   * Copyright (c) 2019 Kristaps Dzonsons <[email protected]>
>   *
> @@ -26,6 +27,8 @@
>  #include <time.h>
>  
>  #include <openssl/evp.h>
> +#include <openssl/pem.h>
> +#include <openssl/x509.h>
>  
>  #include "extern.h"
>  
> @@ -567,4 +570,100 @@ rsc_print(const X509 *x, const struct rs
>  
>       if (outformats & FORMAT_JSON)
>               printf("\t],\n");
> +}
> +
> +/*
> + * Read a file, extract the encapsulated X509 cert and print in PEM format.
> + * Return zero on success, non-zero on failure.
> + */
> +int
> +print_pem(char *fn)
> +{
> +     BIO *bio_out = NULL;
> +     X509 *x = NULL;
> +     X509_CRL *c = NULL;

It's not 100% consistent, but most rpki-client code aligns variables:

        BIO             *bio_out = NULL;
        X509            *x = NULL:

etc.

> +     struct gbr *gbr = NULL;
> +     struct mft *mft = NULL;
> +     struct roa *roa = NULL;
> +     struct rsc *rsc = NULL;
> +     unsigned char *buf;

Initialize buf to NULL here.

> +     size_t len;
> +     enum rtype type;
> +     int rc = 1;
> +
> +     x509_init_oid();
> +
> +     type = rtype_from_file_extension(fn);
> +     if (type == RTYPE_INVALID) {
> +             buf = NULL;

Drop buf = NULL here.

> +             warnx("%s: unsupported file type", fn);
> +             goto out;
> +     }
> +
> +     if ((buf = load_file(fn, &len)) == NULL) {
> +             warnx("load_file failed");
> +             goto out;
> +     }
> +
> +     if ((bio_out = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL)
> +             errx(1, "BIO_new_fp");
> +
> +     switch (type) {
> +     case RTYPE_CER:
> +             if ((x = d2i_X509(NULL, (const unsigned char **)&buf, len))
> +                 == NULL) {

d2i functions have a trap: they advance the buf pointer you pass in past
what they parse (so that you can pass buf to the next d2i function if
necessary). That's why you got a crash due to a bogus free at the end
for CER and CRL files.

The right way to handle this is to add "const unsigned char *der;" next
to buf at the top and do

                der = buf;
                if ((x = d2i_X509(NULL, &der, len)) == NULL) {

then you can free(buf) unconditionally at the end.

> +                     warnx("d2i_X509 failed");
> +                     goto out;
> +             }
> +             break;
> +     case RTYPE_CRL:
> +             if ((c = d2i_X509_CRL(NULL, (const unsigned char **)&buf, len))
> +                 == NULL) {

Similarly here with der = buf.

> +                     warnx("d2i_X509_CRL failed");
> +                     goto out;
> +             }
> +             if (!X509_CRL_print(bio_out, c))
> +                     errx(1, "X509_CRL_print");
> +             if (!PEM_write_bio_X509_CRL(bio_out, c))
> +                     errx(1, "PEM_write_bio_X509_CRL");
> +             rc = 0;
> +             goto out;
> +     case RTYPE_GBR:
> +             if ((gbr = gbr_parse(&x, fn, buf, len)) == NULL)
> +                     goto out;
> +             break;
> +     case RTYPE_MFT:
> +             if ((mft = mft_parse(&x, fn, buf, len)) == NULL)
> +                     goto out;
> +             break;
> +     case RTYPE_ROA:
> +             if ((roa = roa_parse(&x, fn, buf, len)) == NULL)
> +                     goto out;
> +             break;
> +     case RTYPE_RSC:
> +             if ((rsc = rsc_parse(&x, fn, buf, len)) == NULL)
> +                     goto out;
> +             break;
> +     default:
> +             errx(1, "%s: unhandled", fn);
> +     }
> +
> +     if (!X509_print(bio_out, x))
> +             errx(1, "X509_print");
> +
> +     if (!PEM_write_bio_X509(bio_out, x))
> +             errx(1, "PEM_write_bio_X509");
> +
> +     rc = 0;
> + out:
> +     BIO_free(bio_out);
> +     if (type != RTYPE_CER && type != RTYPE_CRL)
> +             free(buf);

buf should be freed unconditionally.

> +     X509_free(x);
> +     X509_CRL_free(c);
> +     gbr_free(gbr);
> +     mft_free(mft);
> +     roa_free(roa);
> +     rsc_free(rsc);
> +     return rc;
>  }
> Index: rpki-client.8
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rpki-client/rpki-client.8,v
> retrieving revision 1.68
> diff -u -p -r1.68 rpki-client.8
> --- rpki-client.8     30 Jun 2022 10:27:52 -0000      1.68
> +++ rpki-client.8     24 Aug 2022 23:33:25 -0000
> @@ -37,6 +37,8 @@
>  .Op Fl t Ar tal
>  .Fl f
>  .Ar
> +.Nm
> +.Fl p Ar file
>  .Sh DESCRIPTION
>  The
>  .Nm
> @@ -144,6 +146,12 @@ If the
>  and
>  .Fl j
>  options are not specified this is the default.
> +.It Fl p Ar file
> +Print the encapsulated DER encoded X.509 certificate or CRL in
> +.Ar file
> +in both human-readable and
> +.Em PEM
> +format.
>  .It Fl R
>  Synchronize via RSYNC only.
>  .It Fl r
> 

Reply via email to