skoerfgen / acmecert Goto Github PK
View Code? Open in Web Editor NEWPHP client library for Let's Encrypt and other ACME v2 - RFC 8555 compatible Certificate Authorities
Home Page: https://github.com/skoerfgen/ACMECert
License: MIT License
PHP client library for Let's Encrypt and other ACME v2 - RFC 8555 compatible Certificate Authorities
Home Page: https://github.com/skoerfgen/ACMECert
License: MIT License
Hi, is it possible to request a certificate with http-01 challenges which includes many certificates in one?
For example: website.com, *.website.com, *.sub1.website.com, *.sub2.website.com, *.sub3.website.com
Thank you
I am creating a multitenant solution in PHP, every user that signup will get a subdomain name like e.g john.domain.com, peter.domain.com.
Im using a DNS TXT record to route all subdomains to www.domain.com, where I do some PHP magic to extract the subdomain name, e.g john and then server john the correct content.
Of course I want the john.domain.com to be secure, using let's encrypt certificate, so that the user have to enter https://john.domain.com
As far as I understand I have to run a function on every signup, e.g when john signup I have to issue a new certificate on the subdomain john.domain.com using this code:
$domain_config=array('john.example.com'=>array('challenge'=>'dns-01'));
And then every 80 days or so I have to run through all subdomains to revalidate them, something like this:
$domain_config=array('john.example.com'=>array('challenge'=>'dns-01'));
$domain_config=array('peter.example.com'=>array('challenge'=>'dns-01'));
Is this correct or am I completely wrong?
Or is a better approach using:
$domain_config=array('*.example.com'=>array('challenge'=>'dns-01'))
Hi,
I recently upgraded my PHP installation to php 8 and the function openssl_pkey_free() triggers a deprecated message.
Thank you
This is a script about the dnsapi, It has many bugs, How to better cooperate with ACMECert
Hi, the limit of pending authorizations is at 300 per account. Is this lib making sure those are always getting trigger to avoid reaching the pending limit? Thanks!
How do we make this script support
Elliptic Curve Parameters
Diffie-Hellman Parameters
I believe it would be much beneficial to all users of this script.
saw an implementation but uses proc_open , proc_close , etc most of which are disabled in some hosting.
First of all, thank you so much for developing this awesome tool! I highly appreciate all of you guys putting in the hours of work to make this happen.
I'm trying to use http-01 challenges on a Basic Authentification (htaccess + htpasswd) secured domain.
Without any additional efforts, I'm getting the following response. Unfortunately I don't know how to "fix" this.
Fatal error: Uncaught ACME_Exception: Challenge validation failed: Invalid response from http://_securedomain.tld_/.well-known/acme-challenge/2DPe2qEWB4LEYLJqJFRhdRooDJAlDXUBVzpeYxTjX3o [2a01:488:42:1000:b24d:6bf2:fff9:445d]: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n \"http://www.w3.org/TR/xhtml1/D" (urn:ietf:params:acme:error:unauthorized) in /is/htdocs/.../ACMECert/ACMECert.php:220
PS: Edited out the exact path and domain info for security reasons.
I'm running identical scripts on two different severs. It's working properly at one server, but on a shared hosting it's running into the following error, whenever trying to create a CSR (generateCSR) or key (generateKey):
Error in GnuTLS initialization: Failed to acquire random data.
Both servers are running on PHP version 7.2.29-he.0 and have OpenSSL/1.0.1t enabled. Any ideas of what needs to be enabled/updated/changed on the shared hosting?
Please provide some examples!
there is a small TTL delay between 2 DNS requests to the identical zone. this is an issue if your're going to sign a certificate for a domain AND a wildcard.
so when you set up the first TXT record for *.example.com and LE will validate it, the second validation for example.com will fail, because LE resolver still has only the TXT record for the first request in cache
Fix: you should be able to set up all TXT records for a zone and THEN fire the validation request
// example code
// (replaced my domain with example.com in code and log)
$ac=new ACMECert(false);
$ac->loadAccountKey('file://account_key.pem');
$privkey=$ac->generateRSAKey();
$domain_config=array(
'example.com'=>array('challenge'=>'dns-01'),
'*.example.com'=>array('challenge'=>'dns-01')
);
$handler=function($opts) use ($ac){
echo 'Create DNS-TXT-Record '.$opts['key'].' with value '.$opts['value']."\n";
// code to set up DNS TXT record
return function($opts){
// code to remove this TXT record
};
};
$fullchain=$ac->getCertificateChain($privkey,$domain_config,$handler);
// log:
Generating CSR
Getting account info
Initializing ACME v2 staging environment
https://acme-staging-v02.api.letsencrypt.org/directory [200 OK] (0.35s)
Initialized
https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce [200 OK] (0.32s)
https://acme-staging-v02.api.letsencrypt.org/acme/new-acct [200 OK] (0.23s)
AccountID: https://acme-staging-v02.api.letsencrypt.org/acme/acct/118150
Account info retrieved
Creating Order
https://acme-staging-v02.api.letsencrypt.org/acme/new-order [201 Created] (2.16s)
Order created: https://acme-staging-v02.api.letsencrypt.org/acme/order/118150/24075821
Fetching authorization (1/2)
https://acme-staging-v02.api.letsencrypt.org/acme/authz/cMwkKLmHSg2pAXGcwsd_10531NhE3_hz05TXkM822e8 [200 OK] (0.24s)
Triggering challenge callback for example.com [dns-01]
Create DNS-TXT-Record _acme-challenge.example.com with value cpFMEUlL6CcefDIiSsiMlhCCcrihB0-mh2rqAwSa4WE
Notifying server for validation
https://acme-staging-v02.api.letsencrypt.org/acme/challenge/cMwkKLmHSg2pAXGcwsd_10531NhE3_hz05TXkM822e8/251283352 [200 OK] (0.25s)
Waiting for server challenge validation
https://acme-staging-v02.api.letsencrypt.org/acme/authz/cMwkKLmHSg2pAXGcwsd_10531NhE3_hz05TXkM822e8 [200 OK] (0.24s)
Validation successful: example.com
Fetching authorization (2/2)
https://acme-staging-v02.api.letsencrypt.org/acme/authz/Kw4FP2B5oQPq-XUigqcE6wkO662pIqhJMsKcmbCYE88 [200 OK] (0.23s)
Triggering challenge callback for *.example.com [dns-01]
Create DNS-TXT-Record _acme-challenge.example.com with value okw-z8Pz5bda50LleTR8S_hv9W_s1xTdSKBINucQNgA
Notifying server for validation
https://acme-staging-v02.api.letsencrypt.org/acme/challenge/Kw4FP2B5oQPq-XUigqcE6wkO662pIqhJMsKcmbCYE88/251283351 [200 OK] (0.24s)
Waiting for server challenge validation
https://acme-staging-v02.api.letsencrypt.org/acme/authz/Kw4FP2B5oQPq-XUigqcE6wkO662pIqhJMsKcmbCYE88 [200 OK] (0.24s)
Validation failed: *.example.com
PHP Fatal error: Uncaught exception 'Exception' with message 'Challenge validation failed: Incorrect TXT record "cpFMEUlL6CcefDIiSsiMlhCCcrihB0-mh2rqAwSa4WE" found at _acme-challenge.example.com (urn:ietf:params:acme:error:unauthorized)' in ACMECert.php:178
Stack trace:
#0 acmev2.php(98): ACMECert->getCertificateChain('-----BEGIN PRIV...', Array, Object(Closure))
#1 {main}
thrown in ACMECert.php on line 178
Fatal error: Uncaught exception 'Exception' with message 'Challenge validation failed: Incorrect TXT record "cpFMEUlL6CcefDIiSsiMlhCCcrihB0-mh2rqAwSa4WE" found at _acme-challenge.example.com (urn:ietf:params:acme:error:unauthorized)' in ACMECert.php:178
Stack trace:
#0 acmev2.php(98): ACMECert->getCertificateChain('-----BEGIN PRIV...', Array, Object(Closure))
#1 {main}
thrown in ACMECert.php on line 178
As title.
Hi, I'm getting an error
[15-Apr-2020 18:22:27 Asia/Krasnoyarsk] PHP Fatal error: Uncaught exception 'Exception' with message 'Could not load account key: file://account_key.pem (error:02001002:system library:fopen:No such file or directory)' in ...\ACMECert.php:464
Stack trace:
#0 ...\ACMECert\index.php(26): ACMEv2->loadAccountKey('file://account_...')
#1 {main}
thrown in ...\ACMECert.php on line 464
in PHP version 5.6.40 x64. But I do not get errors in version 5.6.31 x86. Searching the Internet, I assume that openssl cannot find the configuration file, can I specify it somewhere in the code?
My code:
if(isset($_POST['Register_Account_Key_with_Lets_Encrypt'])){
require 'ACMECert.php';
$ac = new ACMECert();
$ac->loadAccountKey('file://'.'account_key.pem');
$ret=$ac->register(true,'[email protected]');
print_r($ret);
echo '<a href="./">back</a>';
exit;
}
At which point is file cert_private_key.pem
created? I keep getting an error that cert_private_key.pem
doesn't exist:
$fullchain=$ac->getCertificateChain('file://'.'cert_private_key.pem',$domain_config,$handler);
file_put_contents('fullchain.pem',$fullchain);
Is this something I need to create manually? I seem to be missing something.
Dear Stefan, is it possible to specify two DNS-names in one wildcard certificate? If I write:
$domain_config=array(
'example.com'=>array('challenge'=>'dns-01'),
'*.example.com'=>array('challenge'=>'dns-01')
);
then it will require me two DNS-TXT records. And also I cannot use this code, it will cause an error:
$domain_config=array(
'example.com,*.example.com'=>array('challenge'=>'dns-01')
);
In certbot, when creating a certificate, you can use the following command, this will add two DNS-names to the certificate:
certbot certonly --manual --email [email protected] -d *.example.com -d example.com --agree-tos --manual-public-ip-logging-ok --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory
Would it be possible to look into implementing alternate trust paths please? Last week LetsEncrypts DST Root CA X3 expired, causing a lot of trouble for older SSL implementations, especially on servers. There's a really easy fix available: using an alternate trust path. While this breaks Android-4 trust it fixes trust for OpenSSL/LibreSSL.
When you download the certificates in the last step, the ACME server may provide a header in this format:
link: https://acme-v02.api.letsencrypt.org/acme/cert/0123456789abcdef0123456789abcdef/1;rel="alternate"
That is the link to an alternative trust path that a sysadmin may choose to use instead of the default path.
Resources:
https://letsencrypt.org/2020/12/21/extending-android-compatibility.html
https://datatracker.ietf.org/doc/html/rfc8555#section-7.4.2
I created a quick and dirty fix that suits my needs, but it would be great if this were a supported feature.
Hello,
Thanks for your work, PHP Fatal error: Uncaught Exception: Could not load account key: 'account_key.pem ' (error:0E06D06C:configuration file routines:NCONF_get_string:no value)
Do you know what is wrong ?
I'm using a WAMP Server with php 7.3
Thank you,
There are some issues with PHP 8.1, I will try to do PR with a fix later this week once I have time.
Deprecated: Optional parameter $termsOfServiceAgreed declared before required parameter $eab_hmac is implicitly treated as a required parameter in ACMECert.php on line 43
Fatal error: Uncaught Exception: Could not load Private Key or CSR (Optional parameter $termsOfServiceAgreed declared before required parameter $eab_hmac is implicitly treated as a required parameter | error:02001002:system library:fopen:No such file or directory): file://cert_private_key.pem in ACMECert.php:294
I think moving the option param $termsOfServiceAgreed
to the end would fix it.
Hi,
Thanks for this great library. Was able to get to the end and get a fullchain.pem
file.
I need a .crt and .key file for apache, and not able to figure out how to get that.
Tried the following two:
openssl rsa -in fullchain.pem -out keyfileOutName.key
openssl pkey -in fullchain.pem -out foo.key
but get an error:
unable to load key
648:error:0906D06C:PEM routines:PEM_read_bio:no start line:.\crypto\pem\pem_lib.c:701:Expecting: ANY PRIVATE KEY
Any help greatly appreciated.
When I try to generate CSR key and parse it like this:
$domain_private_key = $ac->generateRSAKey(2048);
//$domain_private_key = preg_replace("/^\xEF\xBB\xBF/", '', $domain_private_key);
$domain_csr = $ac->generateCSR($domain_private_key, ['apple.highschoolhelper.org']);
$ret = $ac->parseCertificate($domain_csr);
echo "<pre>";
print_r($ret);
echo "</pre>";
Then I get this exception:
Warning: openssl_x509_read(): supplied parameter cannot be coerced into an X509 certificate!
in /var/www/html/apple.highschoolhelper.org/public_html/libs/ACMECert/ACMECert.php on line 313
Fatal error: Uncaught Exception: Could not load certificate:
-----BEGIN CERTIFICATE REQUEST----- MIICojCCAYoCAQAwJTEjMCEGA1UEAwwaYXBwbGUuaGlnaHNjaG9vbGhlbHBlci5v cmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUcL30x/uEDLoVl3Sw +fadN5ElHEohR33z/5oNAun+tpGATRplNiiO7mfWHXLY6g39anhPaHFBGLd3KSSF hZobM7D7B+F6A1Q2IAJLQ//6xopUagIUl6IuMn6xgXCNoV75D97vAAYjM8Q8iwOM mCo3ayhtOjRlmf5XiIiSBq5jbQgCVUn5wcGp2XGseofYYiK4ZA+54b9k5UJJ3pHh 5rERJ93G3JgSUQaXPLtgOpBB2XSS8trfedQWbx79Fb2rSPYdMeZCwV78pkl6gSAR DfeGvatCsVAZcerRLVbCZlfK1RSQ7hQA7HkZJIbU4Bk8hMF9yeJV33q6oYBnP6xZ Rm33AgMBAAGgODA2BgkqhkiG9w0BCQ4xKTAnMCUGA1UdEQQeMByCGmFwcGxlLmhp Z2hzY2hvb2xoZWxwZXIub3JnMA0GCSqGSIb3DQEBDQUAA4IBAQAdfcUvD5csPYFF Asgc8wlPFWcKOWEfTBKCTLLN9f5p0rOJB7YYxrG0nvlbSrDNgB+G60hFcdXfRgeY 1P0JAplLZTYv4JyfFTJyNtwZidjwq0IPe171Mqv7GzXiaGj1qgZTUBeLqwybX+K9 v81zGKrRrx5B30YViNBY/b3/ErFaDnFzX5NOHKBRHrhXkUFQTDRxpXBGJrIb61l4 Ix4cnHbGwHAMVO+8A0cEWHqG4W3lYdRwMb+/jyf8RJ3/qTq4EAtdY7xMMs2kJsnM nFwq12z1CwOStETUSErDhmRzzYGuG29bg4sn2GQHtTP8B2e8vpFJJVlDYIH/SPi8 nuA0v3cW -----END CERTIFICATE
in /var/www/html/apple.highschoolhelper.org/public_html/libs/ACMECert/ACMECert.php on line 314
Hi Stefan
Have you been thinking about adding EAB authorization to the lib? ZeroSSL is a new kid on the block but requires EAB support. BuyPASS works out of the box (only CA's URL need to be added). Look at the acme.sh or ACMEphp projects for details on how it works.
ZeroSSL is a very interesting CA as there are almost no rate limits.
Chris
Hello
Really love your work especially with this multi Ca feature (i.e ability to use the class not for just LetsEncrypt alone but also ZeroSsl and BuyPass).
In the comments you promised to merge this branch with Master. May I make a suggestion ?
I noticed none of the class methods and function have comments / comment headers e.g
public function generateECKey() {
}
ought to look this way
/**
* Generate Elliptic Curve (EC) private key (used as account key or private key for a certificate).
* @param string $curve_name Default is 'P-384'
* Supported Curves by Let’s Encrypt: P-256 (prime256v1), P-384 (secp384r1), P-521 (secp521r1)
* @return Returns the generated EC private key as PEM encoded string. Throws an Exception if the EC key could not be generated.
* /
public function generateECKey() {
}
Would have made the changes myself BUT don't know Git much. Guess when I have more time, would try knowing some GIT.
I understand these information already are on the index page but we need not always consult that page to get more information on what a class method does.
Once again, Really appreciate the work.
Thanks
Google Trust Services dns-01 verification method constantly says invalid as an answer and cannot verify. I would be glad if you could check it.
i don't know where but there must be an memory leak when u try to get a lot of certificates in a loop.
code example test.php
to reproduce,
require 'ACMECert/ACMECert.php';
use skoerfgen\ACMECert\ACMECert;
for($i=0;$i<100;$i++) {
$ac=new ACMECert('https://acme-staging-v02.api.letsencrypt.org/directory');
$ac->loadAccountKey('file://'.'account_key.pem');
$hostname=substr(md5(uniqid().microtime()),0,5).'.hostname.tld';
$private_host_key=$ac->generateECKey('P-384');
file_put_contents("cert_private_key.$hostname.pem",$private_host_key);
$domain_config=array(
$hostname=>array('challenge'=>'http-01','docroot'=>'/var/www/vhosts/wildcard.hostname.tld/')
);
$handler=function($opts){
$fn=$opts['config']['docroot'].$opts['key'];
@mkdir(dirname($fn),0777,true);
file_put_contents($fn,$opts['value']);
return function($opts){
unlink($opts['config']['docroot'].$opts['key']);
};
};
$fullchain=$ac->getCertificateChain('file://'."cert_private_key.$hostname.pem",$domain_config,$handler);
file_put_contents("fullchain.$hostname.pem",$fullchain);
echo "$i: memory_get_usage=".(memory_get_usage()/1024)."kB\n";
}
example output of a cli php test.php | grep memory_get_usage
using php8.2
memory_get_usage=579.96875kB
memory_get_usage=597.1328125kB
memory_get_usage=614.28125kB
memory_get_usage=631.4296875kB
memory_get_usage=648.578125kB
memory_get_usage=665.7265625kB
memory_get_usage=682.875kB
memory_get_usage=700.0234375kB
memory_get_usage=717.171875kB
memory_get_usage=734.3203125kB
memory_get_usage=751.46875kB
memory_get_usage=768.6171875kB
memory_get_usage=785.765625kB
memory_get_usage=802.9140625kB
i know the PHP GC cleans it up, but the script itself should do it
Couldn't you provide a complete and simple example document? From the first step to the final certificate issuance and renewal, it feels like the order is confusing and there is no complete step by step, I don't know how the order is
I am using the latest ACMECert.php file and testing my script against Let's Encrypt Staging.
My account info is fine but when I use the getCertificateChain method to get a signed cert for my wildcard cert for my domain, I get the following in the console.
I created the CSR myself and passing that into getCertificateChain via file://
Getting account info
Initializing ACME v2 staging environment
Using cURL
https://acme-staging-v02.api.letsencrypt.org/directory [200] (0.26s)
Initialized
https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce [200] (0.06s)
https://acme-staging-v02.api.letsencrypt.org/acme/new-acct [200] (0.12s)
AccountID: https://acme-staging-v02.api.letsencrypt.org/acme/acct/********
Account info retrieved
Using provided CSR
Creating Order
https://acme-staging-v02.api.letsencrypt.org/acme/new-order [201] (0.14s)
Order created: https://acme-staging-v02.api.letsencrypt.org/acme/order/********/66862812
All authorizations already valid, skipping validation altogether
Finalizing Order
https://acme-staging-v02.api.letsencrypt.org/acme/finalize/********/66862812 [400] (0.12s)
PHP Fatal error: Uncaught ACME_Exception: Error parsing certificate request: asn1: syntax error: sequence truncated (urn:ietf:params:acme:error:malformed) in /home/dhuv/git/ACMECert/ACMECert.php:718
Stack trace:
#0 /home/dhuv/git/ACMECert/ACMECert.php(561): ACMEv2->http_request('https://acme-st...', '{"protected":"e...')
#1 /home/dhuv/git/ACMECert/ACMECert.php(234): ACMEv2->request('_tmp', Array)
#2 /home/dhuv/lets-encrypt/acme-test(29): ACMECert->getCertificateChain('file:///home/dh...', Array, Object(Closure))
#3 {main}
thrown in /home/dhuv/git/ACMECert/ACMECert.php on line 718
I went to line 718 and the $headers variable was the following:
Array
(
[server] => nginx
[date] => Mon, 23 Dec 2019 02:32:05 GMT
[content-type] => application/problem+json
[content-length] => 158
[connection] => keep-alive
[boulder-requester] => ********
[cache-control] => public, max-age=0, no-cache
[link] => https://acme-staging-v02.api.letsencrypt.org/directory;rel="index"
[replay-nonce] => 0002i5_LDNIh8N_0PrIb8p7r6DO5Q1MwPXjL_2qGk9ahO5g
)
Hi, thank you for this awsome work.
I'm trying to make a manual process for certifying with http-01 challenge (somthing like sslforfree). I have read all of the functions mentioned in the documentation but i couldn't find how can i get the html challenge token string to place it in "/.well-known/acme-challenge". i know it shall be in $opt function but if it is possible give me more information about it.
also i have discovered that it is necessary to locate "cacert.pem" in "php.ini" to run ACMECert correctly on "wamp/xampp" (I didn't test it on actual server yet) . users should uncomment "curl.cainfo" and "openssl.cafile" in php.ini with the cacert address.
Not all DNS servers have a fast TTL. A DNS entry can take up to 24/48 hours to be published.
Is there a way to get the callanges without waiting for the ACME protocol?
I would like to use a database entry that first makes DNS requests before it is processed by the ACME protocol to ensure that the `_acme-challenge? entry is already published.
If I solve the whole thing (as it exists now in the example) via a cronjob, then the process waits until if i execute the script further. It generates creates overhead unnecessarily.
case 'dns-01':
echo 'Create DNS-TXT-Record '.$opts['key'].' with value '.$opts['value']."\n";
// Wait here up to 24/48 hours, because the DNS entry is not published
// If the _acme-challenge with $opts['value'] is currently not available
// the script will be waiting here, but it generates lots of overhead because
// the script is here running all the time until the challenges exist
// and the script then continues
return function($opts){
echo 'Remove DNS-TXT-Record '.$opts['key'].' with value '.$opts['value']."\n";
};
break;
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.