ACME Error with ACME2Certifier and MS CA

tweak

Well-Known Member
Dec 27, 2019
76
2
48
41
Hello,
I've been trying for a while to connect my Proxmox hosts to my Microsoft CA. To do this, I'm running an ACME2Certifier in between.
However, Proxmox fails when trying to create the account on the ACME2Certifier.

All my other systems (OPNsense, Linux) work flawlessly with acme.sh. It's only the Proxmox hosts that are causing issues, thus preventing automated certificate renewal.

Is there any known peculiarity with Proxmox's ACME system?

Please see the attached logs/messages I’m receiving.

Code:
Generating ACME account key..
Registering ACME account..
TASK ERROR: Registration failed: Error: POST to https://acme01.xxx.xxx/acme/newaccount {"status": 403, "type": "urn:ietf:params:acme:error:malformed", "detail": "Malformed request"}

Thanks!
Kind Regards.

Current Hosts for Test is Running PVE 8.4.1 (Enterprise Repo)
 
Just a little Update:
With Proxmox PBS 3.4.6 it's working fine. So where is the difference to PVE ?
 
Hi,
yes, should i provide the output here?
But i am definitly sure it is not an issue from the acme2certifier, because it works without any issue with the proxmox backup server.
So i would promise there is an issue with the implementation in the proxmox ve system.
I would hope that anybody can explain the difference between pbs and pve of the acme-client.

Kind regards
 
acme.sh is only used as source for the DNS plugins, the actual ACME client is Proxmox specific, and different between PVE and PBS..
 
I understand but the log would give an indication what is going wrong on the Proxmox side. There are not so many conditions throwing a 403 during account creation.

Feel free to send the log to grindelsack<at>gmail.com. If there is any chance to enable debugging (debug=True in acme_srv.cfg) - even better
 
@fabian, thanks for the info, now it's confirmed that there is a difference between PBS and PVE.

here is the log from acme2certifier with debug enabled:

Bash:
xxx.xxx.xxx.1 - - [08/Sep/2025:09:38:10 +0000] "GET / HTTP/1.1" 302 5 "-" "pve-acme/0.1"
Directory._config_load()
load_config(Directory:/var/www/acme2certifier/acme_srv/acme_srv.cfg)
Helper.config_profile_load()
Helper.config_profile_load() ended
Helper.ca_handler_load()
Directory._config_parameters_load()
Directory._config_parameters_load() ended
Directory._config_load() ended
Directory.directory_get()
CAhandler._config_load()
load_config(CAhandler:/var/www/acme2certifier/acme_srv/acme_srv.cfg)
CAhandler._config_host_load()
CAhandler._config_host_load() ended
CAhandler._config_credentials_load()
CAhandler._config_credentials_load() ended
CAhandler._config_parameters_load()
Helper.config_enroll_config_log_load()
Helper.config_enroll_config_log_load() ended with: False
Helper.config_allowed_domainlist_load()
Helper.config_allowed_domainlist_load() ended with: []
CAhandler._config_parameters_load()
Helper.config_eab_profile_load()
_config_profile_load() ended
Helper.config_profile_load()
Helper.config_profile_load() ended
_config_header_info()
_config_header_info() ended
CAhandler._config_proxy_load()
CAhandler._config_proxy_load() ended
Helper.radomize_parameter_list()
CAhandler._config_load() ended
Directory._directory_get()
Directory._directory_get_meta()
Directory._directory_get_meta() ended
Directory._directory_get() ended
xxx.xxx.xxx.1 /directory
xxx.xxx.xxx.1 - - [08/Sep/2025:09:38:10 +0000] "GET /directory HTTP/1.1" 200 702 "-" "pve-acme/0.1"
Nonce.nonce_generate_and_add()
Nonce.nonce__new()
got nonce: d05dca6798334d3e95bb65a4037fb6f5
DBStore.nonce_add(d05dca6798334d3e95bb65a4037fb6f5)
DBStore.nonce_add() ended
Nonce.generate_and_add() ended with:d05dca6798334d3e95bb65a4037fb6f5
xxx.xxx.xxx.1 - - [08/Sep/2025:09:38:10 +0000] "GET /acme/newnonce HTTP/1.1" 204 0 "-" "pve-acme/0.1"
_config_load()
Helper.eab_handler_load()
Helper.error_dict_get()
Account._config_load()
Account._config.load(): loading eab_handler
Helper.eab_handler_load()
Account._config_load() ended
Account.new()
Message.check()
Helper.decode_message()
Message._check()
Message._nonce_check()
Nonce.check_nonce()
Nonce.nonce._check_and_delete(d05dca6798334d3e95bb65a4037fb6f5)
DBStore.nonce_check(d05dca6798334d3e95bb65a4037fb6f5)
DBStore.nonce_check() ended
DBStore.nonce_delete(d05dca6798334d3e95bb65a4037fb6f5)
DBStore.nonce_delete() ended
Nonce._check_and_delete() ended with:200
Nonce.check_nonce() ended with:200
Message._nonce_check() ended with: 200
Message._name_get(): content: {'jwk': {'use': 'sig', 'kty': 'RSA', 'e': 'AQAB', 'n': 'xripLdggJO4-7Xm8qiANK5Xtsjkt9PB4UbZtd2fGv4wlQQ_zAcFHbMKPEioiZ0aTWPTH4TStNAJraSM23bbgJzsBmU4EflsGkK5OwIzhgpFWeybPIWVNfgY22UaMemIfVVm9nLvAzWd-8kUA3RE0GX5uvyLv21fWrcpORp9vmy_ZnBceswLjzIL3v_C5ymxl4JIJIUaASPtBJh_nAxpXfsxHzuYo5hnwuYPx1w7NsPoH_1Br1LtS6xDz0P30LJ-FSMg-PiDHQc48r4N6p-2BIRlurHG6tihdaqwkostTfPeK7azxHT-A3kg34S2OO63bHX8BU2FOX2UT3viKqXLZOnpmoqZeYRS7jChAXgn_t0T2oY94i4zE6A4cJ3BjMqwPM6FLSYzPXajNoISV1EqORV7GTZ2PgUDDh8MTSLMHt_s-xHXvXvKyG2VCJyn89wbhiTlXN3CxKDwN5DuN1IAJEUauvQp5_ZEYLrXLuYBeuh2mVvjSmEcsCh0ka2qF8MdYsUrH4Vt3aRgw6T9S-NjG5alve83HG1YQkwODznuAhH9h6aid8wN_kSlUqcf9triY-bQmPyAn9Uz5Sz_u0HZQIiAFGTC-Ebn8wB7LHX_BstY4byzjXt_Nj67zgp1lXgS1lt2jpSD-bxwLr7n6G7byu84UhdbzS22RMzrLsLstKhk'}, 'url': 'https://acme01.domain.xxx/acme/newaccount', 'alg': 'RS256', 'nonce': 'd05dca6798334d3e95bb65a4037fb6f5'}
Message._name_get(): server_name: https://acme01.domain.xxx url: https://acme01.domain.xxx/acme/newaccount
Message._name_get() returns: None
Helper.error_dict_get()
Signature.check(None)
check signature against key includedn in jwk
Helper.signature_check(False)
Helper.signature_check(): load plain json
Helper.signature_check() ended with: True, None
Signature.check() ended with: True:None
Message._check() ended with: 200
Message.check() ended with:200
Account._new()
Account._eab_check()
Account._eab_jwk_compare()
Helper.b64decode_pad()
_eab_jwk_compare() ended with: False
Account._eab_check() ended with: 403
Account._new() ended with: 403
Message.prepare_response()
Error.enrich_error()
Error.acme_errormessage(urn:ietf:params:acme:error:malformed)
Nonce.nonce_generate_and_add()
Nonce.nonce__new()
got nonce: f0912464f1d74bef94623e46b79f7613
DBStore.nonce_add(f0912464f1d74bef94623e46b79f7613)
DBStore.nonce_add() ended
Nonce.generate_and_add() ended with:f0912464f1d74bef94623e46b79f7613
Account.new() returns: {"code": 403, "header": {"Replay-Nonce": "f0912464f1d74bef94623e46b79f7613"}, "data": {"status": 403, "type": "urn:ietf:params:acme:error:malformed", "detail": "Malformed request"}}
xxx.xxx.xxx.1 /acme/newaccount {'code': 403, 'header': {'Replay-Nonce': '- modified -'}, 'data': {'status': 403, 'type': 'urn:ietf:params:acme:error:malformed', 'detail': 'Malformed request'}}
xxx.xxx.xxx.1 - - [08/Sep/2025:09:38:11 +0000] "POST /acme/newaccount HTTP/1.1" 403 105 "-" "pve-acme/0.1"

kind regards
 
Thank you. I see that the key-comparison between inner and outer public as defined in RFC8555 section 7.3.4 Step 5 fails. I would like to see what Proxmox is sending (most likely they either send an incomplete key or stripping some elements). We can continue to debug this but I suggest that we do this as part of an a2c issue. Let me know what you prefer.
 
For me it make no difference where the issue would be handlet.
I only hope it could be fixed. So if it would be required i open a case in your git.

But it look like there are changes in the proxmox acme client required.

Kind regards
 
thx!
I think, that is the relevant block

Perl:
sub new_account {
 361     my ($self, $tos_url, %info) = @_;
 362     my $url = $self->_method('newAccount');
 363
 364     my %payload = (contact => $info{contact});
 365
 366     if (defined($info{eab})) {
 367         my $eab_hmac_key;
 368         if ($info{eab}->{hmac_key} =~ m/[+\/]/) {
 369             $eab_hmac_key = decode_base64($info{eab}->{hmac_key});
 370         } else {
 371             $eab_hmac_key = decode_base64url($info{eab}->{hmac_key});
 372         }
 373         $payload{externalAccountBinding} = external_account_binding_jws(
 374             $info{eab}->{kid}, $eab_hmac_key, $self->jwk(), $url,
 375         );
 376     }
 377
 378     if ($tos_url) {
 379         $self->{tos} = $tos_url;
 380         $payload{termsOfServiceAgreed} = JSON::true;
 381     }
 382
 383     return $self->__new_account(201, $url, 1, %payload);
 384 }