Gufo ACME Examples: Register ACME Account
We have mastered how to create a Certificate
Signing Request in our get_csr
example. This guide will drive you through
an ACME account registration process.
An account registration process is crucial
to perform all other operations, ACME
is intended to: The certificate signing.
acme_register.py import asyncio
import sys
from gufo.acme.clients.base import AcmeClient
DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
async def main ( email : str , client_state_path : str ) -> None :
client_key = AcmeClient . get_key ()
async with AcmeClient ( DIRECTORY , key = client_key ) as client :
await client . new_account ( email )
state = client . get_state ()
with open ( client_state_path , "wb" ) as fp :
fp . write ( state )
if __name__ == "__main__" :
asyncio . run ( main ( sys . argv [ 1 ], sys . argv [ 2 ]))
The code is straightforward:
acme_register.py import asyncio
import sys
from gufo.acme.clients.base import AcmeClient
DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
async def main ( email : str , client_state_path : str ) -> None :
client_key = AcmeClient . get_key ()
async with AcmeClient ( DIRECTORY , key = client_key ) as client :
await client . new_account ( email )
state = client . get_state ()
with open ( client_state_path , "wb" ) as fp :
fp . write ( state )
if __name__ == "__main__" :
asyncio . run ( main ( sys . argv [ 1 ], sys . argv [ 2 ]))
AcmeClient is an asynchronous client, so we
need asyncio.run()
function to launch it.
acme_register.py import asyncio
import sys
from gufo.acme.clients.base import AcmeClient
DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
async def main ( email : str , client_state_path : str ) -> None :
client_key = AcmeClient . get_key ()
async with AcmeClient ( DIRECTORY , key = client_key ) as client :
await client . new_account ( email )
state = client . get_state ()
with open ( client_state_path , "wb" ) as fp :
fp . write ( state )
if __name__ == "__main__" :
asyncio . run ( main ( sys . argv [ 1 ], sys . argv [ 2 ]))
Import sys
module to parse the CLI argument.
Warning
We use sys.argv
only for demonstration purposes. Use argsparse
or alternatives
in real-world applications.
acme_register.py import asyncio
import sys
from gufo.acme.clients.base import AcmeClient
DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
async def main ( email : str , client_state_path : str ) -> None :
client_key = AcmeClient . get_key ()
async with AcmeClient ( DIRECTORY , key = client_key ) as client :
await client . new_account ( email )
state = client . get_state ()
with open ( client_state_path , "wb" ) as fp :
fp . write ( state )
if __name__ == "__main__" :
asyncio . run ( main ( sys . argv [ 1 ], sys . argv [ 2 ]))
Then we import an AcmeClient
itself.
acme_register.py import asyncio
import sys
from gufo.acme.clients.base import AcmeClient
DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
async def main ( email : str , client_state_path : str ) -> None :
client_key = AcmeClient . get_key ()
async with AcmeClient ( DIRECTORY , key = client_key ) as client :
await client . new_account ( email )
state = client . get_state ()
with open ( client_state_path , "wb" ) as fp :
fp . write ( state )
if __name__ == "__main__" :
asyncio . run ( main ( sys . argv [ 1 ], sys . argv [ 2 ]))
The crucial ACME protocol concept is the Directory . The directory
is an URL which allows to fetch all necessary information about
ACME server. In our case we're using Letsencrypt staging directory.
Warning
The staging server should be used only for testing purposes.
Replace the DIRECTORY
variable with the productive
endpoint to get the real certificates.
acme_register.py import asyncio
import sys
from gufo.acme.clients.base import AcmeClient
DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
async def main ( email : str , client_state_path : str ) -> None :
client_key = AcmeClient . get_key ()
async with AcmeClient ( DIRECTORY , key = client_key ) as client :
await client . new_account ( email )
state = client . get_state ()
with open ( client_state_path , "wb" ) as fp :
fp . write ( state )
if __name__ == "__main__" :
asyncio . run ( main ( sys . argv [ 1 ], sys . argv [ 2 ]))
We define the main
function to wrap our code. It assepts
the following parameters:
email
- an account email.
client_state
- a path to where we can save the state
of the client to reuse it later.
Note
The main function is asynchronous
acme_register.py import asyncio
import sys
from gufo.acme.clients.base import AcmeClient
DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
async def main ( email : str , client_state_path : str ) -> None :
client_key = AcmeClient . get_key ()
async with AcmeClient ( DIRECTORY , key = client_key ) as client :
await client . new_account ( email )
state = client . get_state ()
with open ( client_state_path , "wb" ) as fp :
fp . write ( state )
if __name__ == "__main__" :
asyncio . run ( main ( sys . argv [ 1 ], sys . argv [ 2 ]))
The client uses secret key to sign all communications to
the server. Later, this key will be bound to account.
We use AcmeClient.get_key()
function to generate
a new key.
acme_register.py import asyncio
import sys
from gufo.acme.clients.base import AcmeClient
DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
async def main ( email : str , client_state_path : str ) -> None :
client_key = AcmeClient . get_key ()
async with AcmeClient ( DIRECTORY , key = client_key ) as client :
await client . new_account ( email )
state = client . get_state ()
with open ( client_state_path , "wb" ) as fp :
fp . write ( state )
if __name__ == "__main__" :
asyncio . run ( main ( sys . argv [ 1 ], sys . argv [ 2 ]))
AcmeClient
requires two mandatory parameters:
ACME Directory URL.
The client key.
We use async with
construct to initialize the client
and make it available within the block.
acme_register.py import asyncio
import sys
from gufo.acme.clients.base import AcmeClient
DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
async def main ( email : str , client_state_path : str ) -> None :
client_key = AcmeClient . get_key ()
async with AcmeClient ( DIRECTORY , key = client_key ) as client :
await client . new_account ( email )
state = client . get_state ()
with open ( client_state_path , "wb" ) as fp :
fp . write ( state )
if __name__ == "__main__" :
asyncio . run ( main ( sys . argv [ 1 ], sys . argv [ 2 ]))
We use new_account()
call to register the new account.
Since then the client is considered bound and we can
use it for other operations.
acme_register.py import asyncio
import sys
from gufo.acme.clients.base import AcmeClient
DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
async def main ( email : str , client_state_path : str ) -> None :
client_key = AcmeClient . get_key ()
async with AcmeClient ( DIRECTORY , key = client_key ) as client :
await client . new_account ( email )
state = client . get_state ()
with open ( client_state_path , "wb" ) as fp :
fp . write ( state )
if __name__ == "__main__" :
asyncio . run ( main ( sys . argv [ 1 ], sys . argv [ 2 ]))
Client key and account information is required for any
account manipulations. So we save them for later usage.
acme_register.py import asyncio
import sys
from gufo.acme.clients.base import AcmeClient
DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
async def main ( email : str , client_state_path : str ) -> None :
client_key = AcmeClient . get_key ()
async with AcmeClient ( DIRECTORY , key = client_key ) as client :
await client . new_account ( email )
state = client . get_state ()
with open ( client_state_path , "wb" ) as fp :
fp . write ( state )
if __name__ == "__main__" :
asyncio . run ( main ( sys . argv [ 1 ], sys . argv [ 2 ]))
Open file for write, note the state has bytes
type, so
we need to use wb
option to write a binary file.
Then write our state.
acme_register.py import asyncio
import sys
from gufo.acme.clients.base import AcmeClient
DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
async def main ( email : str , client_state_path : str ) -> None :
client_key = AcmeClient . get_key ()
async with AcmeClient ( DIRECTORY , key = client_key ) as client :
await client . new_account ( email )
state = client . get_state ()
with open ( client_state_path , "wb" ) as fp :
fp . write ( state )
if __name__ == "__main__" :
asyncio . run ( main ( sys . argv [ 1 ], sys . argv [ 2 ]))
If we're called from command line, get a command line arguments:
Account email
State path
Running
Run the example:
python3 examples/acme_register.py mymail@mydomain.com /tmp/acme.json
Check the /tmp/acme.json
file:
/tmp/acme.json {
"directory": "https://acme-staging-v02.api.letsencrypt.org/directory",
"key": {
"n": "qhd84f-9Wb...5AQQ",
"e": "AQAB",
"d": "Sgan5MoDNC..Fk9cw",
"p": "6BPvgdy6_i..gkdM",
"q": "u5_dOHJqNh..bRRs",
"dp": "C9PYRPoG3..MVf9k",
"dq": "LQ5U14tSS..iRIGU",
"qi": "5AcvleFCl..jBFsQ"
},
"account_url": "https://acme-staging-v02.api.letsencrypt.org/acme/acct/1234567"
}
Conclusions
In this section we have mastered the process of the account registration.
Now we're ready to a major ACME's step: A sertificate signing.