Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add EAB functions #704

Merged
merged 1 commit into from
Oct 26, 2022
Merged

add EAB functions #704

merged 1 commit into from
Oct 26, 2022

Conversation

lodott72
Copy link
Contributor

@lodott72 lodott72 commented Sep 16, 2021

Shell function get_eab_json() that generates the EAB field required in register requests when EAB is required.

Based on the array EAB_PARAMS it generates the json string EAB_JSON.

If the array has a single entry, it is treated as file path and keyid and hmac-key are read from it.

If it has two entries, they are treated as keyid string and hmac-key as base64url-encoded binary key (64 bytes).

That's how certbot for example treats their --eac-hmac-key argument.

It also requires base64url_decode which was added as well - it uses awk and might need some OS related tweaking.

For now I am using these functions in a separate script that sources getssl (--source), so it would be helpful to have them in getssl already.

I can make that separate script available as well, if it is of interest.

@timkimber timkimber self-requested a review November 11, 2021 21:54
@timkimber timkimber self-assigned this Nov 11, 2021
@timkimber
Copy link
Member

Hi @lodott72

Thanks for contributing this change, I need to read up on EAB as I've not heard of that and then see if getssl should support the --eac-hmac-key argument as well.

@lodott72
Copy link
Contributor Author

Just to help you out there - RFC 8555 Section 7.3.4

An example of a CA which expects EAB is

% curl https://acme.sectigo.com/v2/OV {
"newNonce": "https://acme.sectigo.com/v2/OV/newNonce",
"newAccount": "https://acme.sectigo.com/v2/OV/newAccount",
"newOrder": "https://acme.sectigo.com/v2/OV/newOrder",
"revokeCert": "https://acme.sectigo.com/v2/OV/revokeCert",
"keyChange": "https://acme.sectigo.com/v2/OV/keyChange",
"meta": {
"termsOfService": "https://secure.trust-provider.com/repository/docs/Legacy/20201020_Certificate_Subscriber_Agreement_v_2_4_click.pdf",
"caaIdentities": ["sectigo.com", "trust-provider.com", "usertrust.com", "comodoca.com", "comodo.com"],
"externalAccountRequired": true
}
}

To obtain the binding you need to send an additional field externalAccountBinding with the newAccount request, see spec.

The value is the EAB_JSON generated by the function added in my pull request.

The parameters KID, HMAC-KEY are obtained from the CA and at least in case of sectigo are used up once successfully bound.

It took a while to find out that the approach with hexkey is the correct one, matching certbot - sectigo recommends using certbot to do this but I wanted it to work with getssl as well.

@Ayesh
Copy link
Contributor

Ayesh commented Apr 14, 2022

sectigo recommends using certbot to do this but I wanted it to work with getssl as well
Google Cloud Platform also plans to issue certificates from its root, and requires support for EAB in the acme-client. I'm not aware of any client apart from Certbot that supports EAB, and I'm happy to see a PR for it in my preferred client. Thank you.

I will test this out and try to provide feedback.

@lodott72
Copy link
Contributor Author

sectigo recommends using certbot to do this but I wanted it to work with getssl as well
Google Cloud Platform also plans to issue certificates from its root, and requires support for EAB in the acme-client. I'm not aware of any client apart from Certbot that supports EAB, and I'm happy to see a PR for it in my preferred client. Thank you.

I will test this out and try to provide feedback.

This is the script I have been (successfully) using with the Sectigo CA. It is a reduced version of the main getssl script, sourcing its functions and main code block reduced to the parts needed for the EAB process.

I tried to attach this as file but failed both with .txt and .gz for some reason - I left out the details for the get_eab_json function which are in the commit, to keep it a little shorter

#!/usr/bin/env bash
# bind external account based on getssl

# source getssl defaults and functions
SCRIPTDIR=$(dirname "$0")
source "${SCRIPTDIR}/getssl" --source

# custom eab functions - merge into getssl?
base64url_decode() {
    awk '{ if (length($0) % 4 == 3) print $0"="; else if (length($0) % 4 == 2) print $0"=="; else print $0; }' | tr -- '-_' '+/' | base64 -d
}

get_eab_json() { # calculate json block for external account bindings, v2 only
...
}

# override some defaults for eab
CA="https://acme.sectigo.com/v2/OV"
ACCOUNT_KEY=
# force debugging output to show
_USE_DEBUG=1

# parse command-line
while [[ -n ${1+defined} ]]; do
  case $1 in
    -i | --eab-kid)
      shift;
      EAB_KID="$1"
      ;;
    -k | --eab-hmac-key)
      shift;
      EAB_HMAC_KEY="$1"
      ;;
    -a | --account-key)
      shift
      ACCOUNT_KEY="$1"
      ;;
    *)
      error_exit "invalid command line - unsupported argument $1"
      ;;
  esac
  shift
done

# make sure we have eab params
[ -z "${EAB_KID}" ] && error_exit "missing EAB_KID, use -i"
[ -z "${EAB_HMAC_KEY}" ] && error_exit "missing EAB_HMAC_KEY, use -k"
EAB_PARAMS=("${EAB_KID}" "${EAB_HMAC_KEY}")

# check for existing account key
[ ! -s "$ACCOUNT_KEY" ] && error_exit "Account key missing"

# Get the current OS, so the correct functions can be used for that OS. (sets the variable os)
get_os

# directory request
obtain_ca_resource_locations

# must be API v2 for EAB
[[ $API -ne 2 ]] && error_exit "require API v2"

# Check if awk supports json_awk (required for ACMEv2)
json_awk_test=$(json_awk '{ "test": "1" }' 2>/dev/null)
if [[ "${json_awk_test}" == "" ]]; then
    error_exit "Your version of awk does not work with json_awk (see http:https://github.com/step-/JSON.awk/issues/6), please install a newer version of mawk or gawk"
fi

# check if eab is required for CA
EAB_REQUIRED=$(echo "$ca_all_loc" | grep "externalAccountRequired" | awk -F'"' '{print $3}')
debug $EAB_REQUIRED

# nothing to do if no eab expected
[ "$EAB_REQUIRED" != ": true" ] && error_exit "no eab required for $CA"

# use account key to register with CA
# currently the code registers every time, and gets an "already registered" back if it has been.
get_signing_params "$ACCOUNT_KEY"

# get the EAB block
get_eab_json
if [[ "$ACCOUNT_EMAIL" ]]; then 
    regjson='{"termsOfServiceAgreed": true, "contact": ["mailto: '$ACCOUNT_EMAIL'"], "externalAccountBinding" : '$EAB_JSON' }'
else
    regjson='{"termsOfServiceAgreed": true, "externalAccountBinding" : '$EAB_JSON' }'
fi
send_signed_request "$URL_newAccount"  "$regjson"

# gracefully exit ( tidying up temporary files etc).
graceful_exit

@timkimber
Copy link
Member

@lodott72 code looks good, I'll see if I can merge it into the main getssl script over the next day or two. I'll also have a look and see if sectigo provide developer accounts so I can test!

@timkimber
Copy link
Member

Hi @lodott72

I never did find the time to look at the eab functions, apart from discovering that they are needed for one of the other ACME providers. I'll merge this so I can use your functions to add the support for them when I get time to make enhancements

@timkimber timkimber merged commit 5c8e4a4 into srvrco:master Oct 26, 2022
@sc2317
Copy link

sc2317 commented May 12, 2023

I am looking for an idea on how to use EAB key ID and HMAC key using getssl. I think its already implemented but not sure how to pass those values to getssl. Do I need to add these values in config file ?
Could you please suggest.

@lodott72
Copy link
Contributor Author

I am looking for an idea on how to use EAB key ID and HMAC key using getssl. I think its already implemented but not sure how to pass those values to getssl. Do I need to add these values in config file ? Could you please suggest.

No, this is used once only to bind your acme key to your external account at the CA. Once bound, the CA will detect the acme key as authorized.

I have used the script I pasted above a few times for Sectigo, you should get by with only minor changes:

  • the shortened get_eab_json() {} can be removed, this is merged into getssl now and will be sourced
  • adjust the CA url if you have another one

Put the script into the same dir as getssl, then run it; use -i, -k, -a to set ID, HMAC and ACCOUNT_KEY

The first two options basically replicate the certbot options --eab-kid and --eab-hmac-key

@sc2317
Copy link

sc2317 commented May 12, 2023

I don't have experience with shell scripting and have limited knowledge(Currently Learning) to Linux based system. I am using ansible to automate issuing of certificates using this script. My OS is customized appliance from VMware, so not all things are supported. However, I am tweaking things to get it worked.
Can I skip ACCOUNT_KEY option because I think this is not required for our internal ACME server as its configured like that ? May be I can comment those lines in your script.
When you say run this script with -i & -k option then how does getsssl uses these values to get the certificate ?

@lodott72
Copy link
Contributor Author

I don't have experience with shell scripting and have limited knowledge(Currently Learning) to Linux based system. I am using ansible to automate issuing of certificates using this script. My OS is customized appliance from VMware, so not all things are supported. However, I am tweaking things to get it worked. Can I skip ACCOUNT_KEY option because I think this is not required for our internal ACME server as its configured like that ? May be I can comment those lines in your script. When you say run this script with -i & -k option then how does getsssl uses these values to get the certificate ?

You need all three parameters for EAB: a CA uses EAB to give you a chance to register a key pair as authorized with the ACME server of the CA.

It works basically like this (AFAIK): the CA gives you a one-time key (ID+HMAC) while you are authorized to the External Account you want the key pair (ACCOUNT_KEY) Bound to.

I figure that the key id is reserved in the ACME server of the CA and the HMAC stored there until you complete the binding.

Once you have the ID+HMAC, you need to sign it with the key-pair you want to put into the reserved key ID.

The script above sends such a signature during the ACME registration step.

The ACME server validates the signature and on success stores your public key in the reserved slot.

From then you can simply use your the key-pair normally during registration, as it will now be found among the authorized keys.

As you have an internal ACME server, maybe there is a simpler solution for you than EAB - depends on your setup, I guess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants