{"id":15557750,"url":"https://github.com/fgasper/p5-net-acme2","last_synced_at":"2025-04-11T19:12:08.236Z","repository":{"id":56179701,"uuid":"117367676","full_name":"FGasper/p5-Net-ACME2","owner":"FGasper","description":"CPAN’s Net::ACME2","archived":false,"fork":false,"pushed_at":"2020-11-22T12:42:30.000Z","size":183,"stargazers_count":3,"open_issues_count":0,"forks_count":7,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-25T15:06:50.198Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FGasper.png","metadata":{"files":{"readme":"README.md","changelog":"Changes","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-01-13T18:26:45.000Z","updated_at":"2020-11-22T12:42:32.000Z","dependencies_parsed_at":"2022-08-15T14:10:35.049Z","dependency_job_id":null,"html_url":"https://github.com/FGasper/p5-Net-ACME2","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FGasper%2Fp5-Net-ACME2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FGasper%2Fp5-Net-ACME2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FGasper%2Fp5-Net-ACME2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FGasper%2Fp5-Net-ACME2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FGasper","download_url":"https://codeload.github.com/FGasper/p5-Net-ACME2/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248465345,"owners_count":21108244,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-02T15:20:33.036Z","updated_at":"2025-04-11T19:12:08.205Z","avatar_url":"https://github.com/FGasper.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NAME\n\nNet::ACME2 - Client logic for the ACME (Let's Encrypt) protocol\n\n  \n\n# SYNOPSIS\n\n    package SomeCA::ACME;\n\n    use parent qw( Net::ACME2 );\n\n    use constant {\n        DIRECTORY_PATH =\u003e '/acme-directory',\n    };\n\n    # %opts are the parameters given to new().\n    sub HOST {\n        my ($class, %opts) = @_;\n\n        # You can make this depend on the %opts if you want.\n        return 'acme.someca.net';\n    }\n\n    package main;\n\n    my $acme = SomeCA::ACME-\u003enew(\n        key =\u003e $account_key_pem_or_der,\n        key_id =\u003e undef,\n    );\n\n    #for a new account\n    {\n        my $terms_url = $acme-\u003eget_terms_of_service();\n\n        $acme-\u003ecreate_account(\n            termsOfServiceAgreed =\u003e 1,\n        );\n    }\n\n    #Save $acme-\u003ekey_id() somewhere so you can use it again.\n\n    my $order = $acme-\u003ecreate_order(\n        identifiers =\u003e [\n            { type =\u003e 'dns', value =\u003e '*.example.com' },\n        ],\n    );\n\n    my $authz = $acme-\u003eget_authorization( ($order-\u003eauthorizations())[0] );\n\n    my @challenges = $authz-\u003echallenges();\n\n    # ... Pick a challenge, and satisfy it.\n\n    $acme-\u003eaccept_challenge($challenge);\n\n    sleep 1 while 'valid' ne $acme-\u003epoll_authorization($authz);\n\n    # ... Make a key and CSR for *.example.com\n\n    $acme-\u003efinalize_order($order, $csr_pem_or_der);\n\n    while ($order-\u003estatus() ne 'valid') {\n        sleep 1;\n        $acme-\u003epoll_order($order);\n    }\n\n    # ... and now fetch the certificate chain:\n\n    my $pem_chain = $acme-\u003eget_certificate_chain($order);\n\nSee `/examples` in the distribution for more fleshed-out examples.\n\nTo use [Let’s Encrypt](http://letsencrypt.org), see\n[Net::ACME2::LetsEncrypt](https://metacpan.org/pod/Net::ACME2::LetsEncrypt).\n\n# DESCRIPTION\n\nThis library implements client logic for the\nACME (Automated Certificate Management Environment) protocol, as\nstandardized in [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.txt)\nand popularized by [Let’s Encrypt](http://letsencrypt.org).\n\n# STATUS\n\nThis is a production-grade implementation. While breaking changes at this\npoint are unlikely, please always check the changelog before upgrading to\na new version of this module.\n\n# FEATURES\n\n- Support for both ECDSA and RSA encrytion.\n- Support for http-01, dns-01, and [tls-alpn-01](https://datatracker.ietf.org/doc/draft-ietf-acme-tls-alpn/) challenges.\n- Comprehensive error handling with typed, [X::Tiny](https://metacpan.org/pod/X::Tiny)-based exceptions.\n- Supports blocking and (experimentally) non-blocking I/O.\n- [Retry POST on `badNonce` errors.](https://tools.ietf.org/html/rfc8555#section-6.5)\n- This is a pure-Perl solution. Most of its dependencies are\neither core modules or pure Perl themselves. XS is necessary to\ncommunicate with the ACME server via TLS; however, most Perl installations\nalready include the necessary logic (i.e., [Net::SSLeay](https://metacpan.org/pod/Net::SSLeay)) for TLS.\n\n    In short, Net::ACME2 will run anywhere that Perl can speak TLS, which is\n    _almost_ everywhere that Perl runs.\n\n# ERROR HANDLING\n\nAll thrown exceptions are instances of [Net::ACME2::X::Generic](https://metacpan.org/pod/Net::ACME2::X::Generic).\nSpecific error classes aren’t yet defined.\n\n# CRYPTOGRAPHY \u0026 SPEED\n\n[Crypt::Perl](https://metacpan.org/pod/Crypt::Perl) provides all cryptographic operations that this library\nneeds using pure Perl. While this satisfies this module’s intent to be\nas pure-Perl as possible, there are a couple of significant drawbacks\nto this approach: firstly, it’s slower than XS-based code, and secondly,\nit loses the security benefits of the vetting that more widely-used\ncryptography libraries receive.\n\nTo address these problems, Net::ACME2 will, after parsing a key, look\nfor and prefer the following XS-based libraries for cryptography instead:\n\n- [Crypt::OpenSSL::RSA](https://metacpan.org/pod/Crypt::OpenSSL::RSA) (based on [OpenSSL](http://openssl.org))\n- [CryptX](https://metacpan.org/pod/CryptX) (based on [LibTomCrypt](http://www.libtom.net/LibTomCrypt/))\n\nIf the above are unavailable to you, then you may be able to speed up\nyour [Math::BigInt](https://metacpan.org/pod/Math::BigInt) installation; see that module’s documentation\nfor more details.\n\n# EXPERIMENTAL: NON-BLOCKING (ASYNCHRONOUS) I/O\n\nBy default, Net::ACME2 uses blocking I/O.\n\nTo facilitate asynchronous/non-blocking I/O, you may give an `async_ua`\nto `new()`. This value must be an object that implements `request()`.\nThat method should mimic [HTTP::Tiny](https://metacpan.org/pod/HTTP::Tiny)’s method of the same name\n**except** that, instead of returning a hash reference, it should return\na promise. (à la [Promise::XS](https://metacpan.org/pod/Promise::XS), [Promise::ES6](https://metacpan.org/pod/Promise::ES6), [Mojo::Promise](https://metacpan.org/pod/Mojo::Promise), etc.)\nThat promise’s resolution should be a single value that mimics\n`HTTP::Tiny::request()`’s return structure.\n\nWhen a Net::ACME2 instance is created with `async_ua`, several of the\nmethods described below return promises. These promises resolve to the values\nthat otherwise would be returned directly in synchronous mode. Any exception\nthat would be thrown in synchronous mode is given as the promise’s rejection\nvalue. This document’s convention to indicate a function that, in\nasynchronous mode, returns a promise is:\n\n    promise($whatever) = ...\n\nThis distribution ships with [Net::ACME2::Curl](https://metacpan.org/pod/Net::ACME2::Curl), a wrapper around\n[Net::Curl::Promiser](https://metacpan.org/pod/Net::Curl::Promiser), which in turns wraps [Net::Curl::Multi](https://metacpan.org/pod/Net::Curl::Multi). This\nprovides out-of-the-box support for Perl’s most widely-used event interfaces;\nsee Net::Curl::Promiser’s documentation for more details.\n\n# METHODS\n\n## _CLASS_-\u003enew( %OPTS )\n\nInstantiates an ACME2 object, which you’ll use for all\ninteractions with the ACME server. %OPTS is:\n\n- `key` - Required. The private key to associate with the ACME2\nuser. Anything that `Crypt::Perl::PK::parse_key()` can parse is acceptable.\n- `key_id` - Optional. As returned by `key_id()`.\nSaves a round-trip to the ACME2 server, so you should give this\nif you have it.\n- `directory` - Optional. A hash reference to use as the\ndirectory contents. Saves a round-trip to the ACME2 server, but there’s\nno built-in logic to determine when the cache goes invalid. Caveat\nemptor.\n- `async_ua` - Optional. Provides a custom UA object to facilitate\nnon-blocking I/O. This object **MUST** implement the interface described above.\n\n## $id = _OBJ_-\u003ekey\\_id()\n\nReturns the object’s cached key ID, either as given at instantiation\nor as fetched in `create_account()`.\n\n## _OBJ_-\u003ehttp\\_timeout( \\[$NEW\\] )\n\nA passthrough interface to the underlying [HTTP::Tiny](https://metacpan.org/pod/HTTP::Tiny) object’s\n`timeout()` method.\n\nThrows an exception if `async_ua` was given to `new()`.\n\n## promise($url) = _CLASS_-\u003eget\\_terms\\_of\\_service()\n\nReturns the URL for the terms of service. Callable as either\na class method or an instance method.\n\n## promise($created\\_yn) = _OBJ_-\u003ecreate\\_account( %OPTS )\n\nCreates an account using the ACME2 object’s key and the passed\n%OPTS, which are as described in the ACME2 spec (cf. `newAccount`).\nBoolean values may be given as simple Perl booleans.\n\nReturns 1 if the account is newly created\nor 0 if the account already existed.\n\nNB: `create_new_account()` is an alias for this method.\n\n## promise($order) = _OBJ_-\u003ecreate\\_order( %OPTS )\n\nReturns a [Net::ACME2::Order](https://metacpan.org/pod/Net::ACME2::Order) object. %OPTS is as described in the\nACME spec (cf. `newOrder`). Boolean values may be given as simple\nPerl booleans.\n\nNB: `create_new_order()` is an alias for this method.\n\n## promise($authz) = _OBJ_-\u003eget\\_authorization( $URL )\n\nFetches the authorization’s information based on the given $URL\nand returns a [Net::ACME2::Authorization](https://metacpan.org/pod/Net::ACME2::Authorization) object.\n\nThe URL is as given by [Net::ACME2::Order](https://metacpan.org/pod/Net::ACME2::Order)’s `authorizations()` method.\n\n## $str = _OBJ_-\u003emake\\_key\\_authorization( $CHALLENGE )\n\nAccepts an instance of [Net::ACME2::Challenge](https://metacpan.org/pod/Net::ACME2::Challenge) (probably a subclass\nthereof) and returns\na key authorization string suitable for handling the given $CHALLENGE.\nSee `/examples` in the distribution for example usage.\n\nIf you’re using HTTP authorization and are on the same server as the\ndomains’ document roots, then look at the handler logic in\n[Net::ACME2::Challenge::http\\_01](https://metacpan.org/pod/Net::ACME2::Challenge::http_01) for a potentially simpler way to\nhandle HTTP challenges.\n\n## promise() = _OBJ_-\u003eaccept\\_challenge( $CHALLENGE )\n\nSignal to the ACME server that the CHALLENGE is ready.\n\n## promise($status) = _OBJ_-\u003epoll\\_authorization( $AUTHORIZATION )\n\nAccepts a [Net::ACME2::Authorization](https://metacpan.org/pod/Net::ACME2::Authorization) instance and polls the\nACME server for that authorization’s status. The $AUTHORIZATION\nobject is then updated with the results of the poll.\n\nAs a courtesy, this returns the $AUTHORIZATION’s new `status()`.\n\n## promise($status) = _OBJ_-\u003efinalize\\_order( $ORDER, $CSR )\n\nFinalizes an order and updates the $ORDER object with the returned\nstatus. $CSR may be in either DER or PEM format.\n\nAs a courtesy, this returns the $ORDER’s `status()`. If this does\nnot equal `valid`, then you should probably `poll_order()`\nuntil it does.\n\n## promise($status) = _OBJ_-\u003epoll\\_order( $ORDER )\n\nLike `poll_authorization()` but handles a\n[Net::ACME2::Order](https://metacpan.org/pod/Net::ACME2::Order) object instead.\n\n## promise($cert) = _OBJ_-\u003eget\\_certificate\\_chain( $ORDER )\n\nFetches the $ORDER’s certificate chain and returns\nit in the format implied by the\n`application/pem-certificate-chain` MIME type. See the ACME\nprotocol specification for details about this format.\n\n# TODO\n\n- Add pre-authorization support if there is ever a production\nuse for it.\n- Expose the Retry-After header via the module API.\n- There is currently no way to fetch an order or challenge’s\nproperties via URL. Prior to ACME’s adoption of “POST-as-GET” this was\ndoable via a plain GET to the URL, but that’s no longer possible.\nIf there’s a need, I’ll consider adding such logic to Net::ACME2.\n(It’s trivial to add; I’d just like to keep things as\nsimple as possible.)\n- Add (more) tests.\n\n# SEE ALSO\n\n[Crypt::LE](https://metacpan.org/pod/Crypt::LE) is another ACME client library.\n\n[Crypt::Perl](https://metacpan.org/pod/Crypt::Perl) provides this library’s default cryptography backend.\nSee this distribution’s `/examples` directory for sample usage\nto generate keys and CSRs.\n\n[Net::ACME](https://metacpan.org/pod/Net::ACME) implements client logic for the variant of this\nprotocol that Let’s Encrypt first deployed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffgasper%2Fp5-net-acme2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffgasper%2Fp5-net-acme2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffgasper%2Fp5-net-acme2/lists"}