GithubHelp home page GithubHelp logo

yourivw / leclient Goto Github PK

View Code? Open in Web Editor NEW
205.0 11.0 99.0 177 KB

An easy-to-use PHP ACME v2 client library, designed to be used with LetsEncrypt.

License: MIT License

PHP 100.00%
letsencrypt acme-v2 acme-protocol acme-challenge letsencrypt-certificates

leclient's Introduction

LEClient

Latest Stable Version Total Downloads License

 

PHP LetsEncrypt client library for ACME v2. The aim of this client is to make an easy-to-use and integrated solution to create a LetsEncrypt-issued SSL/TLS certificate with PHP. The user has to have access to the web server or DNS management to be able to verify the domain is accessible/owned by the user.

Current version

The current version is 1.2.2

Getting Started

These instructions will get you started with this client library. If you have any questions or find any problems, feel free to open an issue and I'll try to have a look at it.

Also have a look at the LetsEncrypt documentation for more information and documentation on LetsEncrypt and ACME.

Prerequisites

The minimum required PHP version is 5.2.0. Version 7.1.0 is required for EC keys. The function generating EC keys will throw an exception when trying to generate EC keys with a PHP version below 7.1.0.

Version 1.0.0 will be kept available, but will not be maintained.

This client also depends on cURL and OpenSSL.

Installing

Using composer:

composer require yourivw/leclient

It is advisable to cut the script some slack regarding execution time by setting a higher maximum time. There are several ways to do so. One is to add the following to the top of the page:

ini_set('max_execution_time', 120); // Maximum execution time in seconds.

Usage

The basic functions and its necessary arguments are shown here. An extended description is included in each class.

As of version 1.1.6, it is also possible to initiate the LEClient with a PSR-3 logger (\Psr\Log\LoggerInterface).


Initiating the client:

use LEClient\LEClient;

$client = new LEClient($email);								// Initiating a basic LEClient with an array of string e-mail address(es).
$client = new LEClient($email, LEClient::LE_STAGING);					// Initiating a LECLient and use the LetsEncrypt staging URL.
$client = new LEClient($email, LEClient::LE_PRODUCTION);				// Initiating a LECLient and use the LetsEncrypt production URL.
$client = new LEClient($email, true);							// Initiating a LECLient and use the LetsEncrypt staging URL.
$client = new LEClient($email, true, $logger);						// Initiating a LEClient and use a PSR-3 logger (\Psr\Log\LoggerInterface).
$client = new LEClient($email, true, LEClient::LOG_STATUS);				// Initiating a LEClient and log status messages (LOG_DEBUG for full debugging).
$client = new LEClient($email, true, LEClient::LOG_STATUS, 'keys/');			// Initiating a LEClient and select custom certificate keys directory (string or array)
$client = new LEClient($email, true, LEClient::LOG_STATUS, 'keys/', '__account/');	// Initiating a LEClient and select custom account keys directory (string or array)

The client will automatically create a new account if there isn't one found. It will forward the e-mail address(es) supplied during initiation, as shown above.


Using the account functions:

$acct = $client->getAccount();  // Retrieves the LetsEncrypt Account instance created by the client.
$acct->updateAccount($email);   // Updates the account with new contact information. Supply an array of string e-mail address(es).
$acct->changeAccountKeys();     // Generates a new RSA keypair for the account and updates the keys with LetsEncrypt.
$acct->deactivateAccount();     // Deactivates the account with LetsEncrypt.

Creating a certificate order instance. If there is an order found, stored locally, it will use this order. Otherwise, it will create a new order. If the supplied domain names don't match the order, a new order is created as well. The construction of the LetsEncrypt Order instance:

$order = $client->getOrCreateOrder($basename, $domains);                          	    // Get or create order. The basename is preferably the top domain name. This will be the directory in which the keys are stored. Supply an array of string domain names to create a certificate for.
$order = $client->getOrCreateOrder($basename, $domains, $keyType);              	    // Get or create order. keyType can be set to "ec" to get ECDSA certificate. "rsa-4096" is default value. Accepts ALGO-SIZE format.
$order = $client->getOrCreateOrder($basename, $domains, $keyType, $notBefore);              // Get or create order. Supply a notBefore date as a string similar to 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss).
$order = $client->getOrCreateOrder($basename, $domains, $keyType, $notBefore, $notAfter);   // Get or create order. Supply a notBefore and notAfter date as a string similar to 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss).

Using the order functions:

use LEClient\LEOrder;

$valid      = $order->allAuthorizationsValid();                             // Check whether all authorizations in this order instance are valid.
$pending    = $order->getPendingAuthorizations($type);                      // Get an array of pending authorizations. Performing authorizations is described further on. Type is LEOrder::CHALLENGE_TYPE_HTTP or LEOrder::CHALLENGE_TYPE_DNS.
$verify     = $order->verifyPendingOrderAuthorization($identifier, $type);  // Verify a pending order. The identifier is a string domain name. Type is LEOrder::CHALLENGE_TYPE_HTTP or LEOrder::CHALLENGE_TYPE_DNS.
$deactivate = $order->deactivateOrderAuthorization($identifier);            // Deactivate an authorization. The identifier is a string domain name.
$finalize   = $order->finalizeOrder();                                      // Finalize the order and generate a Certificate Signing Request automatically.
$finalize   = $order->finalizeOrder($csr);                                  // Finalize the order with a custom Certificate Signing Request string.
$finalized  = $order->isFinalized();                                        // Check whether the order is finalized.
$cert       = $order->getCertificate();                                     // Retrieves the certificate and stores it in the keys directory.
$revoke     = $order->revokeCertificate();                                  // Revoke the certificate without a reason.
$revoke     = $order->revokeCertificate($reason);                           // Revoke the certificate with a reason integer as found in section 5.3.1 of RFC5280.

Supportive functions:

use LEClient\LEFunctions;

LEFunctions::RSAGenerateKeys($directory, $privateKeyFile, $publicKeyFile);	// Generate a RSA keypair in the given directory. Variables privateKeyFile and publicKeyFile are optional and have default values private.pem and public.pem.
LEFunctions::ECGenerateKeys($directory, $privateKeyFile, $publicKeyFile);	// Generate a EC keypair in the given directory (PHP 7.1+ required). Variables privateKeyFile and publicKeyFile are optional and have default values private.pem and public.pem.
LEFunctions::Base64UrlSafeEncode($input);					// Encode the input string as a base64 URL safe string.
LEFunctions::Base64UrlSafeDecode($input);					// Decode a base64 URL safe encoded string.
LEFunctions::log($data, $function);						// Print the data. The function variable is optional and defaults to the calling function's name.
LEFunctions::checkHTTPChallenge($domain, $token, $keyAuthorization);		// Checks whether the HTTP challenge is valid. Performing authorizations is described further on.
LEFunctions::checkDNSChallenge($domain, $DNSDigest);				// Checks whether the DNS challenge is valid. Performing authorizations is described further on.
LEFunctions::createhtaccess($directory);					// Created a simple .htaccess file in the directory supplied, denying all visitors.

Filesystem Structure

LEClient stores account keys, certificate keys, certificates and order data in the filesystem. By default, the folder structure used will look like this, relative to your working directory:

keys/                   Top-level LEClient folder
  public.pem            Your certificate’s public key
  private.pem           Your certificate’s private key
  order                 A file used to store the order URL
  fullchain.crt         The full-chain certificate
  certificate.crt       The certificate
  __account/            An internal folder for LEClient to store your account keys
    public.pem          Your ACME account’s public key
    private.pem         Your ACME account’s private key
    .htaccess           An automatically-generated .htaccess to prevent accidental exposure

You can customise these locations by passing values to the $certificateKeys and $accountKeys construction parameters when creating an LEClient.

Passing strings will change the location and name of the top-level LEClient folder, and the name of the Account Key folder. Note that when passing strings, the account key folder will always be a subfolder of the top-level folder, meaning that:

$client = new LEClient('[email protected]', LEClient::PRODUCTION, LEClient::LOG_OFF, 'path/to/my/key/folder/', 'my_account_folder');

will result in the following structure:

path/to/my/key/folder/
  public.pem
  …
  my_account_folder/
    public.pem
    …

If you want to have more control over the exact locations the various files are stored in, you can instead pass arrays to the $certificateKeys and $accountKeys parameters. If you pass an array to one, you must pass arrays to both.

$client = new LEClient('[email protected]', LEClient::PRODUCTION, LEClient::LOG_OFF, [
  'public_key' => 'path/to/public/key.pem',          // Required
  'private_key' => 'path/to/private/key.pem',        // Required
  'order' => 'path/to/order.txt',                    // Required
  'certificate' => 'path/to/certificate.crt',        // One or both of certificate and fullchain_certificate
  'fullchain_certificate' => 'path/to/fullchain.crt' // must be provided.
], [
  'public_key' => 'path/to/account/public/key.pem',  // Required
  'private_key' => 'path/to/account/private/key.pem' // Required
]);

Authorization challenges

LetsEncrypt (ACME) performs authorizations on the domains you want to include on your certificate, to verify you actually have access to the specific domain. Therefore, when creating an order, an authorization is added for each domain. If a domain has recently (in the last 30 days) been verified by your account, for example in another order, you don't have to verify again. At this time, a domain can be verified by a HTTP request to a file (http-01) or a DNS TXT record (dns-01). The client supplies the necessary data for the chosen verification by the call to getPendingAuthorizations(). Since creating a file or DNS record differs for every server, this is not implemented in the client. After the user has fulfilled the challenge requirements, a call has to be made to verifyPendingOrderAuthorization(). This client will first verify the challenge with checkHTTPChallenge() or checkDNSChallenge() by itself, before it is starting the verification by LetsEncrypt. Keep in mind, a wildcard domain can only be verified with a DNS challenge. An example for both challenges is shown below.

HTTP challenge

For this example, we assume there is one domain left to verify.

use LEClient\LEOrder;

$pending = $order->getPendingAuthorizations(LEOrder::CHALLENGE_TYPE_HTTP);

This returns an array:

Array
(
    [0] => Array
        (
            [type] => http-01
            [identifier] => test.example.org
            [filename] => A8Q1DAVcd_k_oKAC0D_y4ln2IWrRX51jmXnR9UMMtOb
            [content] => A8Q1DAVcd_k_oKAC0D_y4ln2IWrRX51jmXnR9UMMtOb.C4kIiiwfcynb3i48AQVtZRtNrD51z4JiIrdQsgVqcL8
        )
)

For a successful verification, a request will be made to the following URL:

http://test.example.org/.well-known/acme-challenge/A8Q1DAVcd_k_oKAC0D_y4ln2IWrRX51jmXnR9UMMtOb

The content of this file should be set to the content in the array above. The user should create this file before it can verify the authorization.

DNS challenge

For this example, we assume there are two domains left to verify. One is a wildcard domain. The second domain in this example is added for demonstration purposes. Adding a subdomain to the certificate which is also already covered by the wildcard domain is does not offer much added value.

$pending = $order->getPendingAuthorizations(LEOrder::CHALLENGE_TYPE_DNS);

This returns an array:

Array
(
    [0] => Array
        (
            [type] => dns-01
            [identifier] => example.org
            [DNSDigest] => FV5HgbpjIYe1x9MkPI81Nffo2oA-Jo2S88gCL7-Ky5P
        )     
    [1] => Array
        (
            [type] => dns-01
            [identifier] => test.example.org
            [DNSDigest] => WM5YIsgaZQv1b9DbRZ81EwCf2fi-Af2JlgxTC7-Up5D
        )
)

For a successful verification, DNS records should be created as follows:

Name TTL Type Value
_acme-challenge.example.org 60 TXT FV5HgbpjIYe1x9MkPI81Nffo2oA-Jo2S88gCL7-Ky5P
_acme-challenge.test.example.org 60 TXT WM5YIsgaZQv1b9DbRZ81EwCf2fi-Af2JlgxTC7-Up5D

The TTL value can be set higher if wanted or necessary, I prefer to keep it as low as possible for this purpose. To make sure the verification is successful, it would be advised to run a script using DNS challenges in two parts, with a certain amount of time in between to allow for the DNS record to update. The user himself should make sure to set this DNS record before the record can be verified. The DNS record name also depends on your provider, therefore getPendingAuthorizations() does not give you a ready-to-use record name. Some providers only accept a name like _acme-challenge, without the top domain name, for _acme-challenge.example.org. Some providers accept (require?) a full name like shown above.

A wildcard domain, like *.example.org, will be verified as example.org, as shown above. This means the DNS record name should be _acme-challenge.example.org

Full example

For both HTTP and DNS authorizations, a full example is available in the project's main code directory. The HTTP authorization example is contained in one file. As described above, the DNS authorization example is split into two parts, to allow for the DNS record to update in the meantime. While the TTL of the record might be low, it can sometimes take some time for your provider to update your DNS records after an amendment.

If you can't get these examples, or the client library to work, try and have a look at the LetsEncrypt documentation mentioned above as well. In order for the example code to work, make sure to replace all 'example.org' information with your own information. The examples will fail when you run them using the preset example data.

Security

Security is an important subject regarding SSL/TLS certificates, of course. Since this client is a PHP script, it is likely this code is running on a web server. It is obvious that your private key, stored on your web server, should never be accessible from the web. When the client created the keys directory for the first time, it will store a .htaccess file in this directory, denying all visitors. Always make sure yourself your keys aren't accessible from the web! I am in no way responsible if your private keys go public. If this does happen, the easiest solution is to change your account keys (described above) or deactivate your account and create a new one. Next, create a new certificate.

License

This project is licensed under the MIT License - see the LICENSE file for details.

leclient's People

Contributors

0x4r45h avatar a3dho3yn avatar alexnodex avatar anirudhmalhotra avatar barnabywalters avatar crackerben99 avatar czirkoszoltan avatar etiennebruines avatar f4810 avatar fabulousgee avatar fendrychl avatar haoju-tech avatar marksagal avatar meabed avatar milesizzo avatar mvorisek avatar pekapl avatar politsin avatar ptuchik avatar rhurling avatar rogierw avatar sikhlana avatar ubani avatar yourivw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

leclient's Issues

Unable to install via composer

yourivw/leclient 1.1.1 requires php ^5.2 -> your PHP version (7.2.3) does not satisfy that requirement.

From my limited knowledge of composer I believe this should work properly. However I think you should force the use of PHP7+ as PHP 5.6 will stop getting security updates at the end of the year.

"require": {
    "php": ">=5.2",
}

Interestingly enough I can't even get it to properly install and get picked up via autoload with --ignore-platform-reqs, which I think might be from the location of LEClient.

DNS caching

DNS caching cause errors like DNS challenge for 'domain.zone' tested, found invalid.
There must be some way to ignore DNS cache or avoid local checks.

Notice: Undefined index: agreement

I have problem with PHP notice
PHP Notice: Undefined index: agreement in .../vendor/yourivw/leclient/LEClient/src/LEAccount.php:129

In returned data from api is not agreement key.

Array
(
[request] => POST https://acme-v02.api.letsencrypt.org/acme/acct/36057402
[header] => HTTP/1.1 200 OK
Server: nginx
Content-Type: application/json
Content-Length: 875
Boulder-Requester: 36057402
Link: https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf;rel="terms-of-service"
Replay-Nonce: GizlJhrzzEC1BSHXQK0a_JcXPSW9_T_DKLI1C3lLiYc
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800
Expires: Mon, 04 Jun 2018 12:30:07 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache
Date: Mon, 04 Jun 2018 12:30:07 GMT
Connection: keep-alive

[body] => Array
    (
        [id] => 36057402
        [key] => Array
            (
                [kty] => RSA
                [n] => osomet7sFqSb5SLUN6F7jnlDA47yryY9nerjtiUwfKWLk2nZ94ep_c4eGHmENkANgtlxZ4mAlryJkd4ltOftQ0N6jia2zkABWBlRZLMMHqv7V_xAmVra_iB5aFLvs5v0MdvEpv3hPYXT5swEN4iuRSWSygbyX_IiLjUsWRhmEwVqMNYYM-pwrWfBmWDJd6jirkTZC0V1m961HwVBtNz0yijofQUxwju8Z4UOBpUwFrH0_7eIjo3Bx8WVcEEThcQxGJAiq9YmqxH5msuCgXEPzaCTSjxRBAT-zVAq4KLfEIQFaRlodXI1X81YAA8YqHf-A_qLA0H2cEF69bw71aM5BejQlCHgCpP57_a9ZJ0mz7J1xmneCcA-cTP4S0RkQ1Ouhqvn97Lhwx0T3HxgSc5VC-s54YNyfzLPm9k0YxsnXE2LIrsK8Op4SYPRc4Zn9ORNsViQJ_fNWixgGkRaEMSY197v6C-rcQn6LNBhiEKNG-6sxdeNwwNEhIbXpu4aqLXVrHPYrcNZE2nFOlP11btWWOpZHkV2v8l9_KpP1K5mUEKQaS248yjRTiEkcmmuOqgU43up_BQEedjPpDinMuI-G58C7J9Z2755qPQfGVBhwuA3OhWdO5upTGU25mjfJRMoFt00b7xd43LyNljuiGCzm3_U9XLmV-1XrO_Lr4S-scs
                [e] => AQAB
            )

        [contact] => Array
            (
            )

        [initialIp] => 46.234.115.35
        [createdAt] => 2018-06-04T09:53:19Z
        [status] => valid
    )

)

$certificateKeys

If $certificateKeys path is not ended with slash then .htaccess file will be created with wrong path.

$certificateKeys = "folder/domain.tld";

For example, there will be folder/domain.tld.htaccess instead of folder/domain.tld/.htaccess

Invalid response: 400

I'm trying to use this library but could not get too far. The very simple line:

$client = new LEClient($email);

causes HTTP 400 error. Is it possible something have changed in the LE API recently that needs to be incorporated in LEClient?

Using php 7.4.6 & LEclient 1.2.2

Staging: Invalid response, header: HTTP/1.1 405 Method Not Allowed

Using https://acme-staging-v02.api.letsencrypt.org I'm getting the following response:

Invalid response, header: HTTP/1.1 405 Method Not Allowed
Server: nginx
Date: Fri, 13 Dec 2019 11:12:32 GMT
Content-Type: application/problem+json
Content-Length: 103
Connection: keep-alive
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-staging-v02.api.letsencrypt.org/directory>;rel="index"

It looks like, as of the 4th Dec 2019, unauthenticated GET requests have been deprectaed for the staging API. The same will happen for the production API on November 1st, 2020.

ECDSA certificates

Hi,

first of all - thanks for sharing this library on github, it's the best implementation of ACME in php I have seen so far and I'm planning to use it on production (with few modifications - hopefully they can be accepted into upstream).

I've added support for EC key generation in my fork (pekapl/LEClient) and if it's okay with you I will open pull request.

My changes are breaking backward compatibility - for example:

getOrCreateOrder($basename, $domains, $keyType = 'rsa', $notBefore = '', $notAfter = '')

IMO $keyType will be used more often that notBefore and notAfter variables (since in most cases certs are generated for default period).

HTTP 429 - To Many Request

Feature request: see how many request can be made or if there is a cooling down period active. I use this library to power a CMS and request SSL certificates for the sites running this CMS. From time to time a lot of certificates need to be requested and I get a response from the API HTTP/1.1 429 Too Many Requests indicating this is too much.

Would be nice to be able to see before running a bulk of renewals if there are any requests left.

Unexpected exception occurred: Invalid response, header: HTTP/1.1 404 Not Found

Today, when I initialize a new order it will occur the following error, how to fix it? I have update to the latest version but it is still occurred.

Server: nginx
Date: Tue, 05 Nov 2019 02:56:41 GMT
Content-Type: application/problem+json
Content-Length: 110
Connection: keep-alive
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-v02.api.letsencrypt.org/directory>;rel="index"

Variable "$basename" seems have no effect

Hi! I noticed a problem with the variable "$basename" in "LEOrder.php".
I wanted to set a custom directory to store my certificates for each domain. So I tried defining the "$basename" variable. I found the new certificate would still be stored in the directory that "$certificateKeys"(LEClient.php) defined. The $basename seems not to take effect.
Could you please help me solve the problem?

Thanks!

Error: RSA keypair export failed

2018/06/16 05:32:02 [error] 7660#3172: *79 FastCGI sent in stderr: "PHP Warning: openssl_pkey_export(): cannot get key from parameter 1 in htdocs\LEClient\src\LEFunctions.php on line 57
PHP Fatal error: Uncaught RuntimeException: RSA keypair export failed! in htdocs\LEClient\src\LEFunctions.php:57
Stack trace:
#0 htdocs\LEClient\src\LEAccount.php(69): LEFunctions::RSAGenerateKeys(NULL, 'keys//__account...', 'keys//__account...')
#1 htdocs\LEClient\LEClient.php(164): LEAccount->__construct(Object(LEConnector), 1, Array, Array)
#2 htdocs\cert.php(18): LEClient->__construct(Array, true, 1)
#3 {main}
thrown in htdocs\LEClient\src\LEFunctions.php on line 57" while reading response header from upstream, client: xxxxxxxxx, server: xxxxxxxxx, request: "GET /cert.php HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "xxxxxxxxxxx"

Tried to run the example on Windows with nginx+PHP-FastCGI.
If you need more information, let me know.

Uncaught RuntimeException: Invalid response, header: HTTP/1.1 200 OK

Great job taking your time to craft this library.

I encountered an error and after some digging, it turns out Let's Encrypt has made changes to HTTP Status Code for HEAD new-nonce.

More Info:

https://community.letsencrypt.org/t/acme-v2-change-to-http-status-code-for-head-new-nonce/82000

Fix:

File: https://github.com/yourivw/LEClient/blob/master/src/LEConnector.php#L91
Change to:
if(strpos($this->head($this->newNonce)['header'], "200 OK") == false) throw new \RuntimeException('No new nonce.');

File: https://github.com/yourivw/LEClient/blob/master/src/LEConnector.php#L145
Change to:

if( (($method == 'POST' OR $method == 'GET') AND strpos($header, "200 OK") === false AND strpos($header, "201 Created") === false) OR ($method == 'HEAD' AND strpos($header, "200 OK") === false)) { throw new \RuntimeException('Invalid response, header: ' . $header); }

Heads Up!

Fake LE Intermediate X1

Hi,

First of all it's a very nice script. No Nonsense library's or updates just RUN and PLAY.

However,
When i get a certificate it's not working. Because it's signed / provided by "Fake LE Intermediate X1" this has to be "Let's Encrypt Authority X3"

both challenges http and dns are giving the same fullchain.crt

When i run the certbot script it works fine also with wild-card certificates .
The fullchain.pem created by certbot is signed / provided by 'Let's Encrypt Authority X3'.

But i dont like certbot :-) I like to use the PHP client.

Kind Regards,
Rene

Missing 'name' in composer.json and missing release

To make the composer.json file usable, the following changes have to be made:

  1. Add these three lines to composer.json:
    "name": "yourivw/LEClient",
    "type": "library",
    "description": "PHP LetsEncrypt client library for ACME v2",
  1. Create a release numbered 1.1.0 on github (as that is referenced in the example).

After that, people using your library can create a composer.json file like this:

{
    "repositories": [
        {
            "url": "https://github.com/yourivw/LEClient.git",
            "type": "git"
        }
    ],
    "require": {
        "yourivw/LEClient": "^1.1.0"
    }
}

and then do a composer update and a composer install.

Incorrect folder for certificate and fullchain_certificate

The files certificate.crt and fullchain.crt are not being saved in the $basename folder as documented here

// Initiating the order instance. The keys and certificate will be stored in /example.org/ (argument 1) and the domains in the array (argument 2) will be on the certificate.

Instead, they are directly saved as keys/certificate.crt and keys/fullchain.crt. It will overwrite earlier ones when creating multiple certs.

Provide a PSR-4 namespace

Instead of telling to require the file, it would be better to provide a good PSR-4 namespace.

You can do it with ease thanks to composer.

Would you mind consider it?

Order not valid on staging

There is an error with any domain:

06-04-2020 15:42:41, function LEClient __construct:
LEClient finished constructing

06-04-2020 15:42:44, function getCertificate:
Order for 'domain.tld' not valid. Cannot retrieve certificate.

A part of the code:

$client = new LEClient([$email], $use_stage, LEClient::LOG_STATUS, $certificate_keys, $account_keys);

$order = $client->getOrCreateOrder($domain, [$domain, "*.$domain"]);
if ($order->allAuthorizationsValid()) {
    if (!$order->isFinalized()) {
        $order->finalizeOrder();
    }
    if ($order->isFinalized()) {
        $order->getCertificate();
    }
}

PHP Notice: Undefined index: agreement in /var/www/composer/vendor/yourivw/leclient/LEClient/src/LEAccount.php on line 129

When you create an order for a new domain, you get this notice:
PHP Notice: Undefined index: agreement in /var/www/composer/vendor/yourivw/leclient/LEClient/src/LEAccount.php on line 129

To fix, please use array_key_exists() to avoid referencing the 'agreement' key when it doesn't exist.

Line 129. Change this:
$this->agreement = $post['body']['agreement'];
to this:
$this->agreement = array_key_exists('agreement', $post['body']) ? $post['body']['agreement'] : '';

I had seen it doing the same with some other attributes in that same block of code. Probably best to do it for all of the attributes, and don't assume that any particular attribute is present in the body.

When fixed, please issue a new release and push it to composer so we can update using composer.

Thanks!

HTTP status invalid even after HTTP challenge is valid

Hello Youri,

We are very thankful for your work. I see that some of the authorizations are still invalid even after HTTP challenge is valid. Can you check if there is anything wrong in updating of authorizations after the HTTP challenge is passed.

`
[{"type":"http-01","identifier":"xxxxxx.ml","filename":"iWHOlmQ_z7HspdKqmsdflrSI42Fb2U0-WoL26CFKw2s","content":"iWHOlmQ_z7HspdKqmsdflrSI42Fb2U0-WoL26CFKw2s.Ls2L06CdQ5fV1hLv4r5DIVmk-x-DzO_qUMOUhCuGRME"}]
Creating HTTP challenge file http://xxxxxx.ml/.well-known/acme-challenge/iWHOlmQ_z7HspdKqmsdflrSI42Fb2U0-WoL26CFKw2s
[30-01-2020 09:51:32] :
HTTP challenge for 'xxxxxx.ml' valid.

[{"authorizationURL":"https:\/\/acme-v02.api.letsencrypt.org\/acme\/authz-v3\/2548741019","identifier":{"type":"dns","value":"xxxxxx.ml"},"status":"invalid","expires":"2020-02-06T09:51:34Z","challenges":[{"type":"http-01","status":"invalid","error":{"type":"urn:ietf:params:acme:error:unauthorized","detail":"Invalid response from http:\/\/xxxxxx.ml\/.well-known\/acme-challenge\/iWHOlmQ_z7HspdKqmsdflrSI42Fb2U0-WoL26CFKw2s [185.27.134.201]: \"function toNumbers(d){var e=[];d.replace(\/(..)\/g,func\"","status":403},"url":"https:\/\/acme-v02.api.letsencrypt.org\/acme\/chall-v3\/2548741019\/NtejgQ","token":"iWHOlmQ_z7HspdKqmsdflrSI42Fb2U0-WoL26CFKw2s","validationRecord":[{"url":"http:\/\/xxxxxx.ml\/.well-known\/acme-challenge\/iWHOlmQ_z7HspdKqmsdflrSI42Fb2U0-WoL26CFKw2s","hostname":"xxxxxx.ml","port":"80","addressesResolved":["185.27.134.201"],"addressUsed":"185.27.134.201"}]}]}]
`

Revoking certificates problem

Hiya,

First of all thanks for the easy to use project, much appreciated!

After registering a certificate, i tried to revoke it using the revokeCertificate method of the LEOrder class.
This resulted in an error:
"JWK embedded in revocation request must be the same public key as the cert to be revoked"

After googling for a bit i bumped into https://community.letsencrypt.org/t/revocation-behaviour/53223

Using this logic i changed the revokeCertificate method in the LEOrder class:
$sign = $this->connector->signRequestJWK(array('certificate' => $certificate, 'reason' => $reason), $this->connector->revokeCert);
to
$sign = $this->connector->signRequestJWK(array('certificate' => $certificate, 'reason' => $reason), $this->connector->revokeCert, $this->certificateKeys['private_key']);

Inside the signRequestJWK method in the LEConnector class you default the private key to the account private key and this way you use the domain private key, which seems to do the trick.

New problem, there is a timeout error!

The following code, Step = 1, has an error
504 Gateway Time-out
/////////////////////////////////////////////////////////////////////////////
`<?php
header("Content-type: text/html; charset=utf-8");
ini_set('max_execution_time', 120);
include DIR.'/LEClient/vendor/autoload.php';

// Importing the classes.
use LEClient\LEClient;
use LEClient\LEOrder;

session_start();
$userid = $_SESSION['userid'];
$myemail = $_SESSION['email'];
$mydomain = $_SESSION['domain'];

if($userid==0)
{
die('请先登录!');
}

echo "信息>UserID:".$userid.",邮箱:".$myemail.",域名:".$mydomain."
";

$email = array($myemail);
$basename = $mydomain;
$domains = array($basename,'*.'.$basename);

//$domains = array('*.887d.com');

//https://acme-v02.api.letsencrypt.org
//第二参数true是测试,false是正式使用
$client = new LEClient($email, false, LECLient::LOG_STATUS, "cert/$basename/");//第二参数true是测试,false是正式使用,第四个参数是路径
//$client = new LEClient($email, true, LECLient::LOG_DEBUG);

//获取或创建订单。基本名称最好是顶级域名。这将是存储密钥的目录。提供一组字符串域名来为其创建证书。
$order = $client->getOrCreateOrder($basename, $domains);
//var_dump($order);

$step = get_(0,"step");

if(!$order->allAuthorizationsValid())//检查此订单实例中的所有授权是否有效。
{
$pending = $order->getPendingAuthorizations(LEOrder::CHALLENGE_TYPE_DNS);//获取dns验证记录
//var_dump($pending);

echo "<font color=\"red\">请将以下二条记录值分别域名解析成TXT的内容,设置记录名称为(_acme-challenge)。二条记录分二次解析,二次分别验证。注意解析生效后点击底部的验证,二次验证通过后便可获取SSL证书</font><br />";
echo "<table border=\"1\">";
echo "<tr><td>名称</td><td>记录值</td></tr>";
foreach ($pending as $value)
{
    echo "<tr><td>".$value['identifier']."</td><td>".$value['DNSDigest']."</td></tr>";
}
echo "</table>";
echo "<br />";

switch($step)
{
case 0:
{
    if(!empty($pending))
    {
	    foreach($pending as $challenge)
	    {
		    // For the purpose of this example, a fictitious functions creates or updates the ACME challenge DNS record for this domain. 
		    //setDNSRecord($challenge['identifier'], $challenge['DNSDigest']);
	    }
    }
    else
	    echo "获取授权码无效<br />";

    echo "<a href=\"?step=1\">开始验证</a><br />";
    break;
}
case 1:
{
    if(!empty($pending))
    {
		echo "开始验证:<br />";
	    foreach($pending as $challenge)
	    {
		    // For the purpose of this example, a fictitious functions creates or updates the ACME challenge DNS record for this domain.
			$order->verifyPendingOrderAuthorization($challenge['identifier'], LEOrder::CHALLENGE_TYPE_DNS);
            echo "验证成功<br />";
		}
    }
    else
	    echo "获取授权码无效<br />";

    if($order->allAuthorizationsValid())
    {
        getcert($order);//获取证书
    }
    else
        echo "<a href=\"?step=0\">返回</a>&nbsp;|&nbsp;<a href=\"?step=1\">继续验证</a><br />";

    break;
}
}

}
else //所有授权有效了,就获取证书
{
getcert($order);//获取证书
}
// Check once more whether all authorizations are valid before we can finalize the order.

function getcert($order)
{
global $userid,$myemail,$mydomain;
// Finalize the order first, if that is not yet done.
if(!$order->isFinalized()) $order->finalizeOrder();
// Check whether the order has been finalized before we can get the certificate. If finalized, get the certificate.
if($order->isFinalized())
{
$order->getCertificate();
echo "开始保存数据库
";
include("../config_database.php");
include("../conn.php");
$expiretime = date('Y-m-d H:i:s',strtotime("+3 month"));
$result = $db->prepare("select sslid,userid,email,basename,domains,signtime,expiretime from wuk_ssl where basename=?");
$result->bindParam(1,$mydomain);
$result->execute();
if($rs=$result->fetch(PDO::FETCH_ASSOC))
{
$db->exec("update wuk_ssl set signtime='".gettime()."',expiretime='".$expiretime."' where basename='".$mydomain."'");
echo "成功完成SSL证书的续期。
";
}
else
{
$db->exec("insert into wuk_ssl(userid,email,basename,domains,signtime,expiretime) values (".$userid.",'".$myemail."','".$mydomain."','".$mydomain.",*.".$mydomain."','".gettime()."','".$expiretime."')");//添加进数据库
echo "成功完成SSL证书的申请。
";
}
echo "<a href="/ssl.php" target="_top">返回证书管理查看";

	//域名验证
    $result = $db->prepare("select ckdomain_id,userid,domainname,crtime from wuk_check_domain where domainname=?");
    $result->bindParam(1,$mydomain);
    $result->execute();
	$countnum = $result->rowCount();
    if($countnum==0)
	{
		$db->exec("insert into wuk_check_domain(userid,domainname,crtime) values (".$userid.",'".$mydomain."','".gettime()."')");//添加进数据库
	}

	$db=null;
}

}
//////////////////////////////////////////////////////
function gettime() //获取时间
{
ini_set('date.timezone','Asia/Shanghai');
return date("Y-m-d H:i:s",time());
}

function get_($datatype,$getvalue)
{
if($datatype==0)
{
if (isset($_GET[$getvalue]))
$revalue=intval($_GET[$getvalue]);
else
$revalue=0;
}
else
{
if (isset($_GET[$getvalue]))
$revalue=$_GET[$getvalue];
else
$revalue="";
}
return $revalue;
}

function post_($datatype,$getvalue)
{
if($datatype==0)
{
if (isset($_POST[$getvalue]))
$revalue=intval($_POST[$getvalue]);
else
$revalue=0;
}
else
{
if (isset($_POST[$getvalue]))
$revalue=$_POST[$getvalue];
else
$revalue="";
}
return $revalue;
}
?>`

Updates for version 1.1.5 fails work

Server: nginx
Link: <https://acme-v02.api.letsencrypt.org/directory>;rel="index"
Replay-Nonce: 91BH0jpueGqlKWhWCDzlXMOGR3B4RKCE-7OuJGAfAzc
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800
Content-Length: 0
Expires: Mon, 25 Mar 2019 14:19:12 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache
Date: Mon, 25 Mar 2019 14:19:12 GMT
Connection: keep-alive

 in /var/www/html/vendor/yourivw/leclient/src/LEConnector.php:147
Stack trace:
#0 /var/www/html/vendor/yourivw/leclient/src/LEConnector.php(196): LEClient\LEConnector->request('HEAD', 'https://acme-v0...')
#1 /var/www/html/vendor/yourivw/leclient/src/LEConnector.php(91): LEClient\LEConnector->head('https://acme-v0...')
#2 /var/www/html/vendor/yourivw/leclient/src/LEConnector.php(70): LEClient\LEConnector->getNewNonce()
#3 /var/www/html/vendor/yourivw/leclient/src/LEClient.php(156): LEClient\LEConnector->__construct(1, 'https://acme-v0...', Array)
#4 /var/www/html/modules/synapse/letsencrypt/src/Service/LetsEncrypt.php(113): LEClient\LEClient->__construct(Array, 'https://acme-v0...', 1, Array, Array)
#5 /var/www/html/modules/custom/city/city.drush.inc(64): Drupal\letsencrypt\Service\LetsEncrypt->sign('****.****', Array)

Multiple accounts

This is a good library and provides excellent abstraction for development work.

I needed several trials to get through a full validation cycle, but that may be my own limitation.

I am creating an app which utilises this library, and want to include functionality for multiple Let's Encrypt accounts.

Currently, I believe the single-account functionality leads to the same key pair being used for all $emails specified in the class initiation and account functions

Is there a way to store multiple accounts keys? Probably based on email address? Like...
user-domain-tld_privkey.pkcs8.pem
user-domain-tld_pubkey.pem

It's a fatal mistake.

I run and upload an example, running the website as follows:
https://wuknet.net/ssl/LEClient/examples/dns_init.php

PHP is version 7.1

Error:
Fatal error: Uncaught RuntimeException: Invalid response, header: HTTP/1.1 100 Continue Expires: Sun, 16 Jun 2019 02:21:41 GMT Cache-Control: max-age=0, no-cache, no-store Pragma: no-cache HTTP/1.1 400 Bad Request Server: nginx Content-Type: application/problem+json Content-Length: 134 Link: https://acme-staging-v02.api.letsencrypt.org/directory;rel="index" Replay-Nonce: PO0tJRYghpbFjLxRJu5pSky7czYmDo4O0KZyi7bLkd8 Expires: Sun, 16 Jun 2019 02:21:41 GMT Cache-Control: max-age=0, no-cache, no-store Pragma: no-cache Date: Sun, 16 Jun 2019 02:21:41 GMT Connection: close in /home/jcc/wuknet/ssl/LEClient/src/LEConnector.php:150 Stack trace: #0 /home/jcc/wuknet/ssl/LEClient/src/LEConnector.php(187): LEClient\LEConnector->request('POST', 'https://acme-st...', '{"protected":"e...') #1 /home/jcc/wuknet/ssl/LEClient/src/LEAccount.php(114): LEClient\LEConnector->post('https://acme-st...', '{"protected":"e...') #2 /home/jcc/wuknet/ssl/LEClient/src/LEAccount.php(80): LEClient\LEAccount->getLEAccount() #3 /home/jcc in /home/jcc/wuknet/ssl/LEClient/src/LEConnector.php on line 150

Renewal Of Certificates

Hello

First, thank you for the awesome and easy to use client. Most appreciated.

I am sorry, I am not sure if I have missed something obvious, but how does one renew certificates using this client. Do they just come back as pending and the next certificate creation present them to the client for revalidation or something different. Of do I need to run the script again with each domain?

Thanks
Stephen

Error on Renew

I tried to Renew the Certificate but i got an error:

Fatal error: Uncaught RuntimeException: Invalid response, headers: HTTP/1.1 404 Not Found Server: nginx Content-Type: application/problem+json Content-Length: 106 Expires: Fri, 10 Aug 2018 12:25:11 GMT Cache-Control: max-age=0, no-cache, no-store Pragma: no-cache Date: Fri, 10 Aug 2018 12:25:11 GMT Connection: keep-alive in /var/www/web2240/html/letsencrypt.domain.at/LEClient/src/LEConnector.php:146 Stack trace: #0 /var/www/web2240/html/letsencrypt.domain.at/LEClient/src/LEConnector.php(170): LEConnector->request('GET', 'https://acme-v0...') #1 /var/www/web2240/html/letsencrypt.domain.at/LEClient/src/LEAuthorization.php(62): LEConnector->get('https://acme-v0...') #2 /var/www/web2240/html/letsencrypt.domain.at/LEClient/src/LEOrder.php(261): LEAuthorization->__construct(Object(LEConnector), 1, 'https://acme-v0...') #3 /var/www/web2240/html/letsencrypt.domain.at/LEClient/src/LEOrder.php(130): LEOrder->updateAuthorizations() #4 /var/www/web2240/html/ in /var/www/web2240/html/letsencrypt.domain.at/LEClient/src/LEConnector.php on line 146

The body of the response was:
{ "status": "invalid", "expires": "2018-06-01T12:30:35Z", "identifiers": [ { "type": "dns", "value": "*.domain.at" }, { "type": "dns", "value": "domain.at" } ], "authorizations": [ "https://acme-v02.api.letsencrypt.org/acme/authz/r-em1tNAfuYrrcvdHQzy85hE-63F1QQrLdhyVh6boLU", "https://acme-v02.api.letsencrypt.org/acme/authz/zI6AuJt46C_YYa99mqOMZzj-9LjIC5cXGE15J08CePk" ], "finalize": "https://acme-v02.api.letsencrypt.org/acme/finalize/35550320/6608957" }{ "type": "urn:ietf:params:acme:error:malformed", "detail": "Expired authorization", "status": 404 }

Fatal error

<b>Fatal error</b>:  Uncaught RuntimeException: Invalid response, header: HTTP/1.1 100 Continue
Expires: Fri, 01 Jun 2018 10:02:45 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache

HTTP/1.1 415 Unsupported Media Type
Server: nginx
Content-Type: application/problem+json
Content-Length: 168
Replay-Nonce: K1tN6nutOXx633vIPeoqY0DVbFJB-ti8QITKjVQzIjU
Expires: Fri, 01 Jun 2018 10:02:45 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache
Date: Fri, 01 Jun 2018 10:02:45 GMT
Connection: close

 in vendor\yourivw\leclient\LEClient\src\LEConnector.php:145

Certificate renewal

I must be doing something wrong and would like some clarification on certificate renewal.
I am using DNS verification and when I went to renew the certificate I received new dns entries for the domains being renewed. The certificate being ordered was unchanged from the last time and was the first renewal of an original certificate.

I had removed the directory created by LEClient for the previous order before creating this order. The certificate was being renewed about 62 days after the original, so was in the window that is expected to renew without a new validation. Any ideas on how to correct for next time??

Error with composer

Bug #29 seems to still exist: if I do a composer require yourivw/leclient 1.1.1, I get

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for yourivw/leclient 1.1.1 -> satisfiable by yourivw/leclient[1.1.1].
    - yourivw/leclient 1.1.1 requires php ^5.2 -> your PHP version (7.0.28) does not satisfy that requirement.

There is another problem with the composer installation: when I do composer require yourivw/leclient (without specifying version), 1.1.0 gets installed. However, that one has

$headers = array('Accept: application/json', 'Content-Type: application/json');

at LEConnector.php(105), and does not work anymore.

Unable to create an account

Trying to create an account for my local development domain, but for some reason I cannot because the wrong content-type is sent?

[30-04-2018 12:02:39] function createLEAccount (function post):
Array
(
    [request] => POST https://acme-staging-v02.api.letsencrypt.org/acme/new-acct
    [header] => HTTP/1.1 100 Continue
Expires: Mon, 30 Apr 2018 10:02:39 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache

HTTP/1.1 415 Unsupported Media Type
Server: nginx
Content-Type: application/problem+json
Content-Length: 168
Replay-Nonce: g4xzxAeoJtfgIg7enCkT7JLV-0MDtRaTv59QwEHtE7s
Expires: Mon, 30 Apr 2018 10:02:39 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache
Date: Mon, 30 Apr 2018 10:02:39 GMT
Connection: close


    [body] => Array
        (
            [type] => urn:ietf:params:acme:error:malformed
            [detail] => Invalid Content-Type header on POST. Content-Type must be "application/jose+json"
            [status] => 415
        )

)

how to fix this?

Fatal error: Uncaught RuntimeException: Invalid response, header: HTTP/1.1 100 Continue

Fatal error: Uncaught RuntimeException: Invalid response, header: HTTP/1.1 100 Continue
Expires: Wed, 16 Jan 2019 18:57:23 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache

HTTP/1.1 400 Bad Request
Server: nginx
Content-Type: application/problem+json
Content-Length: 134
Replay-Nonce: 9Hx29F8ypaXiZ2jaLIUQkf9tYzDNUoe9ntQwiH9y2h0
Expires: Wed, 16 Jan 2019 18:57:23 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache
Date: Wed, 16 Jan 2019 18:57:23 GMT
Connection: close

in vendor\yourivw\leclient\src\LEConnector.php on line 147

Am running the library on a Wamp server with php 7.2.4

415 Unsupported Media Type

Hi,

since today, i got the following error in the first step on creating an account:
PHP Fatal error: Uncaught exception 'RuntimeException' with message 'Invalid response, header: HTTP/1.1 100 Continue
Expires: Thu, 29 Mar 2018 07:08:06 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache

HTTP/1.1 415 Unsupported Media Type
Server: nginx
Content-Type: application/problem+json
Content-Length: 168
Replay-Nonce: h3QeHPySh-1uLOEIBlpSltlkAfpslJUYjKCNW0C9Fug
Expires: Thu, 29 Mar 2018 07:08:06 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache
Date: Thu, 29 Mar 2018 07:08:06 GMT
Connection: close

best regards
Michael

Fatal error: Uncaught RuntimeException: Invalid response, header

Just following the first example on the readme I get the following error :

03-11-2019 18:22:03, function LEAccount __construct:
No account found, attempting to create account.

What am I missing ?

Fatal error: Uncaught RuntimeException: Invalid response, header: HTTP/1.1 400 Bad Request Server: nginx Date: Sun, 03 Nov 2019 18:22:04 GMT Content-Type: application/problem+json Content-Length: 108 Connection: keep-alive Cache-Control: public, max-age=0, no-cache Link: https://acme-staging-v02.api.letsencrypt.org/directory;rel="index" Replay-Nonce: 0002C5BCX1a-YAkTXophgECLBtki0ecYjwYzGnN9jLuPR30 in /websites/aapi.younit.app/vendor/yourivw/leclient/src/LEConnector.php:156 Stack trace: #0 /websites/aapi.younit.app/vendor/yourivw/leclient/src/LEConnector.php(193): LEClient\LEConnector->request('POST', 'https://acme-st...', '{"protected":"e...') #1 /websites/aapi.younit.app/vendor/yourivw/leclient/src/LEAccount.php(98): LEClient\LEConnector->post('https://acme-st...', '{"protected":"e...') #2 /websites/aapi.younit.app/vendor/yourivw/leclient/src/LEAccount.php(76): LEClient\LEAccount->createLEAccount(Array) #3 /websites/aapi.younit.app/vendor/yourivw/leclient/src/LEClient.php(156): LEClient\LEAccount->__constru in /websites/aapi.younit.app/vendor/yourivw/leclient/src/LEConnector.php on line 156

My code is

require 'vendor/autoload.php';
use LEClient\LEClient;

require_once '_younit/before.php';

$rs = (function() use ($data) {
    $email = ['[email protected]'];
    // $client = new LEClient($email);
    // $client = new LEClient($email, true);
    $client = new LEClient($email, true, LECLient::LOG_STATUS);
    return true;
})();

require_once '_younit/after.php';

Change Account Key Failed

Hi, I had a problem changing my account key. According to the response, it seems that the old key was not contained in inner JWS. But I have generated the key pair and placed them in "key/__account" directory correctly.
Could you help me analyze this problem?
Many thanks!

`

  | ......
23-04-2019 17:46:53, function LEClient __construct:
LEClient finished constructing
23-04-2019 17:46:55, function changeAccountKeys (function post):Array
  | (
  | [request] => POST https://acme-staging-v02.api.letsencrypt.org/acme/key-change
  | [header] => HTTP/1.1 100 Continue
  | Expires: Tue, 23 Apr 2019 17:46:56 GMT
  | Cache-Control: max-age=0, no-cache, no-store
  | Pragma: no-cache
  |  
  | HTTP/1.1 400 Bad Request
  | Server: nginx
  | Content-Type: application/problem+json
  | Content-Length: 154
  | Boulder-Requester: 9006507
  | Link: https://acme-staging-v02.api.letsencrypt.org/directory;rel="index"
  | Replay-Nonce: bn6tIjeWYhXaHm3Y_AD0nrEGz7V9LfZUbhk0O6A_wCw
  | Expires: Tue, 23 Apr 2019 17:46:56 GMT
  | Cache-Control: max-age=0, no-cache, no-store
  | Pragma: no-cache
  | Date: Tue, 23 Apr 2019 17:46:56 GMT
  | Connection: close
  |  
  |  
  | [body] => Array
  | (
  | [type] => urn:ietf:params:acme:error:malformed
  | [detail] => Inner JWS does not contain old key field matching current account key
  | [status] => 400
  | )
  | ......
 
`

Unit-testable, PSR-4 compatible fork - but what do to with it?

Firstly, thanks for creating this!

I created a fork and began making several improvements that I wanted:

  • PSR-4 compatible, so can now be easily installed via composer
  • PSR-2 formatted
  • replaced curl with Guzzle (as this allows easier unit testing)
  • added unit tests (around 80% complete so far)
  • reduction in code complexity (breaking apart methods, refactoring deeply nested ifs - still some work to go)
  • support for a PSR-3 logger
  • added custom exception classes so that the client throws its own exceptions
  • a few bugs fixed along the way

What I'd also really like is to abstract the storage so that you can have the orders, certificates etc stored somewhere other than the local filesystem.

You can see the branch here https://github.com/lordelph/LEClient/tree/polish

I can turn this into a big pull request once I've got the testing completed, but are these changes too much for you? I'm happy to rename my version to avoid confusion - let me know what you would prefer.

LEClient not working on Windows

Hey,
first of all, i love your library. Great job and thanks for providing it on Github. I am trying to run it on Windows and i get the following error message:

$c = new LEClient\LEClient('[email protected]', true);
RuntimeException with message 'Could not generate key pair! Check your OpenSSL configuration. OpenSSL Error: 
error:02001003:system library:fopen:No such process
error:2006D080:BIO routines:BIO_new_file:no such file
error:0E064002:configuration file routines:CONF_load:system lib
error:02001003:system library:fopen:No such process
error:2006D080:BIO routines:BIO_new_file:no such file
error:0E064002:configuration file routines:CONF_load:system lib
'

Any ideas why that happens?
Thanks and greetings
Leo

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.