How to use openssl certificates with an "agent"

If you're like me then you don't like to leave private keys sitting around unencrypted on your filesystem. That means that any of those client certificates that you use for your fun gemini sites like bbs.geminispace.org or station.martinrue.com need to be encrypted.

If you're also like me and browse geminispace with command line tools this becomes a bit of a pain since having to type passwords in all the damn time is annoying.

How do you get around this? Use gpg for your private key encryption! You don't actually have to store the private keys *in* gpg, you just need to encrypt them like you would any sort of confidential data.

If you are using pxc and bash you can create and use a new identity like so:

# Create a new private key and certificate for whatever 
openssl req -x509 -newkey ed25519 -keyout >(gpg -r ${GPG_ADDRESS} -e -o ${CERT_OUT_PATH}/${IDENTITY}-key.pem.gpg) -noenc -out ${CERT_OUT_PATH}/${IDENTITY}-cert.pem -sha256 -subj "/CN=${SITE_OF_INTEREST}"

# Use the new certificate with pxc, if you're using bash or a similar shell
pxc -c ${CERT_OUT_PATH}/${IDENTITY}-cert.pem -k <(gpg -d ${CERT_OUT_PATH}/${IDENTITY}-key.pem.gpg) ${SITE_OF_INTEREST}

The "process redirection" i.e. <() and >() mean you can pipe the data around purely within the shell so you never need to let it touch your disk.

bashrc functions for automation

If you'd like a bash function that will quickly switch between identities when using pxc, use switch_pxc() below. It makes an alias 'pxcc' that sets up pxc with the proper arguments.

switch_pxc() {
  ID="$1"
  CERT="${HOME}/.pxc/identities/${ID}-cert.pem"
  KEY="${HOME}/.pxc/identities/${ID}-key.pem.gpg"
  if [ ! -f "$CERT" ] || [ ! -f "$KEY" ] ; then
    echo "Could not locate $CERT or $KEY"
    return
  fi

  alias pxcc="pxc -c $CERT -k <(gpg -d $KEY)"

  # display the new alias
  alias pxcc
}

You can use the below function to automatically generate new certificates that can be switched via switch_pxc. It accepts two arguments. The first is the certificate identity, and the optional second argument is the path to an existing private key. If no second argument is given then this function will generate a new key.

PXC_GPG_ID="whateveryouwanthere@mydomain.com"
generate_pxc_cert() {
  ID="$1"
  EXISTING_KEY="$2"

  if [ -n "$EXISTING_KEY" ] ; then
    rp="`realpath "$EXISTING_KEY" 2>/dev/null`"
    if [ -z "$EXISTING_KEY" ] ; then
      echo "Could not find realpath for $EXISTING_KEY"
      return
    fi
  fi

  if [ -z "$ID" ] ; then
    echo "No ID provided"
    return
  fi

  CERT="${HOME}/.pxc/identities/${ID}-cert.pem"
  KEY="${HOME}/.pxc/identities/${ID}-key.pem.gpg"
  if [ -f "$CERT" ] || [ -f "$KEY" ] ; then
    echo "Cert $CERT or key $KEY already exist.  Delete them first if you want to make a new one."
    return
  fi

  if [ -n "$EXISTING_KEY" ] ; then
    if [ ! -f "$EXISTING_KEY" ] ; then
      echo "Could not find existing key $EXISTING_KEY"
      return
    fi

    if openssl req -x509 -key <(gpg -d "$EXISTING_KEY") -out "$CERT" -sha256 -subj "/CN=${ID}" ; then
      echo "Successfully generated certificate $CERT"
      # if the key is in ~/.pxc/identities then make paths relative
      REL_KEY_NAME=${EXISTING_KEY##${HOME}/.pxc/identities/}
      if ln -sf "$REL_KEY_NAME" "$KEY" ; then
        echo "Successfully linked $EXISTING_KEY -> $KEY"
        ls -lh "$KEY"
      else
        echo "Could not link $EXISTING_KEY -> $KEY"
      fi
    else
      echo "Could not generate new certificate from existing key $EXISTING_KEY"
    fi
  else
    if [ -z "$PXC_GPG_ID" ] ; then 
      echo "No PXC_GPG_ID set, cannot generate key"
      return
    fi
    KEYTYPE="${KEYTYPE:-ed25519}"
    echo "Generating a new $KEYTYPE key"
    if openssl req -x509 -newkey "$KEYTYPE" -keyout >(gpg -e -r "$PXC_GPG_ID" -o "$KEY") \
      -out "$CERT" -noenc -sha256 -subj "/CN=${ID}" ; then
      echo "Succesfully generated new cert $CERT and new key $KEY"
    else
      echo "Openssl failed to generate $CERT and $KEY"
    fi
  fi

  if [ -f "$CERT" ] && [ -f "$KEY" ] ; then
    echo "Successfully created $CERT and $KEY"
  else
    echo "Failed to create $CERT and $KEY"
  fi
}

Enjoy!