diff --git a/README.rst b/README.rst index 8e2e369..bee1f3d 100644 --- a/README.rst +++ b/README.rst @@ -36,40 +36,53 @@ Generate new key:: Create certificate signing request:: - sysca request --key KEY_FILE [--password-file TXT_FILE] - [--subject DN] [--san ALTNAMES] - [--CA] [--path-length DEPTH] - [--usage FLAGS] [--ocsp-urls URLS] [--crl-urls URLS] - [--issuer-urls URLS] - [--out CSR_FN] + sysca request [-h] --key KEY_FILE [--password-file PSW_FILE] + [--out OUT_FILE] [--outform FMT] [--text] [--rsa-pss] + [--subject DN] [--san GNAMES] [--usage USAGE] [--CA] [--path-length DEPTH] + [--crl-urls URLS] [--issuer-urls URLS] [--ocsp-urls URLS] + [--ocsp-must-staple] [--ocsp-must-staple-v2] [--ocsp-nocheck] + [--permit-subtrees GNAMES] [--exclude-subtrees GNAMES] + [--require-explicit-policy N] [--inhibit-policy-mapping N] + [--inhibit-any N] [--add-policy POLICY] Create selfsigned certificate:: - sysca selfsign --key KEY_FILE --days N [--password-file TXT_FILE] - [--subject DN] [--san ALTNAMES] - [--CA] [--path-length DEPTH] - [--usage FLAGS] [--ocsp-urls URLS] [--crl-urls URLS] - [--issuer-urls URLS] - [--out CRT_FN] + sysca selfsign [-h] [--out OUT_FILE] [--outform FMT] [--text] + --key KEY_FILE [--password-file PSW_FILE] + [--not-valid-before DATE] [--not-valid-after DATE] [--days DAYS] + [--serial-number SN] [--rsa-pss] + [--subject DN] [--san GNAMES] [--usage USAGE] [--CA] [--path-length DEPTH] + [--crl-urls URLS] [--issuer-urls URLS] [--ocsp-urls URLS] + [--ocsp-must-staple] [--ocsp-must-staple-v2] [--ocsp-nocheck] + [--permit-subtrees GNAMES] [--exclude-subtrees GNAMES] + [--require-explicit-policy N] [--inhibit-policy-mapping N] + [--inhibit-any N] [--add-policy POLICY] Sign certificate signing request:: - sysca sign --ca-key KEY_FILE --ca-info CRT_FILE - --request CSR_FILE --days NUM - [--out CRT_FN] [--password-file TXT_FILE] - [--reset] [--subject DN] [--san ALTNAMES] - [--CA] [--path-length DEPTH] - [--usage FLAGS] [--ocsp-urls URLS] [--crl-urls URLS] - [--issuer-urls URLS] + sysca sign [-h] [--out OUT_FILE] [--outform FMT] [--text] --request CSR_FILE + --ca-info CRT_FILE --ca-key KEY_FILE [--password-file PSW_FILE] + [--not-valid-before DATE] [--not-valid-after DATE] [--days DAYS] + [--serial-number SN] [--reset] [--rsa-pss] + [--subject DN] [--san GNAMES] [--usage USAGE] [--CA] [--path-length DEPTH] + [--crl-urls URLS] [--issuer-urls URLS] [--ocsp-urls URLS] + [--ocsp-must-staple] [--ocsp-must-staple-v2] [--ocsp-nocheck] + [--permit-subtrees GNAMES] [--exclude-subtrees GNAMES] + [--require-explicit-policy N] [--inhibit-policy-mapping N] + [--inhibit-any N] [--add-policy POLICY] + Create or update CRL file:: - sysca update-crl [--crl CRL_FILE] [--out CRT_FN] - --ca-key KEY_FILE --ca-info CRT_FILE [--password-file TXT_FILE] - --days NUM [--crl-number NUM] [--delta-crl-number NUM] - [--reason REASON_NAME] - [--revoke-cert CERT_FILE] ... - [--revoke-serial SERIAL] ... + sysca update-crl [-h] [--out OUT_FILE] [--outform FMT] [--text] + --ca-info CRT_FILE --ca-key KEY_FILE [--password-file PSW_FILE] + [--crl CRL_FILE] [--crl-number VER] [--delta-crl-number VER] + [--crl-scope SCOPE] [--crl-reasons REASONS] [--indirect-crl] + [--issuer-urls URLS] [--delta-crl-urls URLS] + [--last-update DATE] [--next-update DATE] [--days DAYS] + [--revoke-certs FN [FN ...]] + [--revoke-serials NUM [NUM ...]] + [--reason REASON] [--invalidity-date DATE] [--revocation-date DATE] Display contents of CRT, CSR or CRL file:: @@ -109,18 +122,25 @@ Create certificate signing request (CSR). Options: +**--key KEY_FILE** + Private key file to create request for. Can be PGP-encrypted. + Can be password-protected. + +**--password-file FN** + Password file for private key. Can be PGP-encrypted. + **--out CSR_FILE** Target file to write Certificate Signing Request to. **--outform PEM|DER** Output file format. PEM is textual format, DER is binary. Default: PEM. -**--key KEY_FILE** - Private key file to create request for. Can be PGP-encrypted. - Can be password-protected. +**--rsa-pss** + Use RSA-PSS padding when signing with RSA key. Note that this setting will + be inherited - certificate will be signed with RSA-PSS if either this flag + is given, CA certificate uses RSA-PSS or CSR uses RSA-PSS. -**--password-file FN** - Password file for private key. Can be PGP-encrypted. +Certifiace fields: **--subject DN** Subject's DistinguishedName which is X509 Name structure, which is collection @@ -144,19 +164,7 @@ Options: Certificate field: Subject_. -**--CA** - The certificate will have CA rights - that means it can - sign other certificates. - - Extension: BasicConstraints_. - -**--path-length** - Applies only for CA certs - limits how many levels on sub-CAs - can exist under generated certificate. Default: Undefined. - - Extension: BasicConstraints_. - -**--san ALT_NAMES** +**--san GNAMES** Specify alternative names for subject as list of comma-separated strings, that have prefix that describes data type. @@ -226,34 +234,23 @@ Options useful only when apps support them: decipher_only If ``key_agreement`` is true, this flag limits use only for data decryption. -**--ocsp-nocheck** - Disable OCSP checking for this certificate. Used for certificates that - sign OCSP status replies. - - Extension: OCSPNoCheck_. - -**--ocsp-must-staple** - Requires that TLS handshake must be done with stapled OCSP response - using ``status_request`` protocol. +**--CA** + The certificate will have CA rights - that means it can + sign other certificates. - Extension: OCSPMustStaple_. + Extension: BasicConstraints_. -**--ocsp-must-staple-v2** - Requires that TLS handshake must be done with stapled OCSP response - using ``status_request_v2`` protocol. +**--path-length** + Applies only for CA certs - limits how many levels on sub-CAs + can exist under generated certificate. Default: Undefined. - Extension: OCSPMustStapleV2_. + Extension: BasicConstraints_. **--crl-urls URLS** List of URLs where certificate revocation lists can be downloaded. Extension: CRLDistributionPoints_. -**--ocsp-urls URLS** - List of URL for OCSP endpoint where validity can be checked. - - Extension: AuthorityInformationAccess_. - **--issuer-urls URLS** List of URLS where parent certificate can be downloaded, in case the parent CA is not root CA. Usually sub-CA certificates @@ -263,9 +260,28 @@ Options useful only when apps support them: Extension: AuthorityInformationAccess_. -**--exclude-subtrees NAME_PATTERNS** - Disallow CA to sign subjects that match patterns. See ``--permit-subtrees`` - for details. +**--ocsp-urls URLS** + List of URL for OCSP endpoint where validity can be checked. + + Extension: AuthorityInformationAccess_. + +**--ocsp-must-staple** + Requires that TLS handshake must be done with stapled OCSP response + using ``status_request`` protocol. + + Extension: OCSPMustStaple_. + +**--ocsp-must-staple-v2** + Requires that TLS handshake must be done with stapled OCSP response + using ``status_request_v2`` protocol. + + Extension: OCSPMustStapleV2_. + +**--ocsp-nocheck** + Disable OCSP checking for this certificate. Used for certificates that + sign OCSP status replies. + + Extension: OCSPNoCheck_. **--permit-subtrees NAME_PATTERNS** Allow CA to sign subjects that match patterns. @@ -289,11 +305,9 @@ Options useful only when apps support them: Extension: NameConstraints_. -**--inhibit-any N** - Disallow special handling of ``any`` policy (2.5.29.32.0) - after N levels. - - Extension: InhibitAnyPolicy_. +**--exclude-subtrees NAME_PATTERNS** + Disallow CA to sign subjects that match patterns. See ``--permit-subtrees`` + for details. **--require-explicit-policy N** Require explicit certificate policy for whole path after N levels. @@ -305,6 +319,12 @@ Options useful only when apps support them: Extension: PolicyConstraints_. +**--inhibit-any N** + Disallow special handling of ``any`` policy (2.5.29.32.0) + after N levels. + + Extension: InhibitAnyPolicy_. + **--add-policy OID:SPECS** Add another PolicyInformation record to certificate with optional qualifiers. @@ -322,11 +342,6 @@ Options useful only when apps support them: Extension: CertificatePolicies_. -**--rsa-pss** - Use RSA-PSS padding when signing with RSA key. Note that this setting will - be inherited - certificate will be signed with RSA-PSS if either this flag - is given, CA certificate uses RSA-PSS or CSR uses RSA-PSS. - sign ~~~~ @@ -347,26 +362,43 @@ Options: **--outform PEM|DER** Output file format. PEM is textual format, DER is binary. Default: PEM. -**--days NUM** - Lifetime for certificate in days. - **--request CSR_FILE** Certificate request file generated by **request** command. -**--ca-key KEY_FILE** - CA private key file. Can be PGP-encrypted. - Can be password-protected. - **--ca-info CRT_FILE** CRT file generated by **request** command. Issuer CA info will be loaded from it. +**--ca-key KEY_FILE** + CA private key file. Can be PGP-encrypted. + Can be password-protected. + **--password-file FN** Password file for CA private key. Can be PGP-encrypted. +**--not-valid-before DATE** + Start of validity period, default: (now - 1h) + +**--not-valid-after DATE** + End of validity period, default: (now + days) + +**--days DAYS** + Lifetime for certificate in days. + +**--serial-number SN** + Use SN instead automatically generated serial number. + **--reset** Do not use any info fields from CSR, reload all info from command line. - Without it, command line arguments override corresponding fields from CSR. + Without it, CSR fields are used and command line arguments can override + corresponding fields in CSR. + +**--rsa-pss** + Use RSA-PSS padding when signing with RSA key. Note that this setting will + be inherited - certificate will be signed with RSA-PSS if either this flag + is given, CA certificate uses RSA-PSS or CSR uses RSA-PSS. + +Certificate fields are the same as in ``request`` command. selfsign ~~~~~~~~ @@ -396,10 +428,7 @@ CRL file can be either direct or indirect: Revoked certificates contain reference to actual CA that issued. Set with option: ``--indirect-crl``. -Options for CRL itself: - -**--crl FN** - Load existing file. Version numbers are reused unless overrided on command line. +Output options: **--out FN** Write output to file. @@ -407,14 +436,21 @@ Options for CRL itself: **--outform PEM|DER** Output file format. PEM is textual format, DER is binary. Default: PEM. -**--days NUM** - Set period that this CRL is valid. +Options for signing: + +**--ca-info CRT_FILE** + CA certificate used for signing. **--ca-key KEY_FILE** CA private key file. Can be PGP-encrypted. Can be password-protected. -**--ca-info CRT_FILE** - CA certificate used for signing. +**--password-file FN** + Password file for CA private key. Can be PGP-encrypted. + +Options for CRL itself: + +**--crl FN** + Load existing file. Version numbers are reused unless overrided on command line. **--crl-number VER** Version number for main CRL. @@ -442,6 +478,9 @@ Options for CRL itself: Extension: CRLIssuingDistributionPoint_. +**--crl-reasons REASONS** + Limit CRL scope to only list of reasons. + **--indirect-crl** CRL list can contain revoked certificates not issued by CRL signer. @@ -452,6 +491,20 @@ Options for CRL itself: Extension: CRLAuthorityInformationAccess_. +**--delta-crl-urls URLS** + Set urls for Delta CRL Distribution Point. + + Extension: FreshestCRL_. + +**--last-update DATE** + Set update time explicitly instead using current timestamp. + +**--next-update DATE** + Set next update time explicitly instead using **--days**. + +**--days NUM** + Set period that this CRL is valid. + Options for adding entries: **--revoke-certs FN [FN ...]** @@ -492,6 +545,9 @@ Options for adding entries: Extension: CRLInvalidityDate_. +**--revocation-date DATE** + Use DATE instead current timestamp. + show ~~~~ @@ -693,3 +749,4 @@ actually used. .. _PolicyConstraints: https://tools.ietf.org/html/rfc5280#section-4.2.1.11 .. _CertificatePolicies: https://tools.ietf.org/html/rfc5280#section-4.2.1.4 .. _critical: https://tools.ietf.org/html/rfc5280#section-4.2 +.. _FreshestCRL: https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.6 diff --git a/sysca/tool.py b/sysca/tool.py index 3bceae4..a034d3d 100644 --- a/sysca/tool.py +++ b/sysca/tool.py @@ -7,8 +7,8 @@ import sys from datetime import datetime, timezone from typing import ( - TYPE_CHECKING, Any, Callable, Dict, List, - Optional, Sequence, Tuple, TypedDict, Union, cast, + TYPE_CHECKING, Any, Callable, Dict, List, Optional, + Sequence, Tuple, Type, TypedDict, Union, cast, ) from cryptography import x509 @@ -505,7 +505,7 @@ class KeyArgs: def opts_password(p: GParser) -> None: - p.add_argument("--password-file", metavar="FN", help="File to load password from") + p.add_argument("--password-file", metavar="PSW_FILE", help="File to load password from") def opts_text(p: GParser) -> None: @@ -517,9 +517,9 @@ def opts_unsafe(p: GParser) -> None: def opts_output(p: GParser) -> None: - p.add_argument("--out", metavar="FN", + p.add_argument("--out", metavar="OUT_FILE", help="File to write output to, instead stdout") - p.add_argument("--outform", default="PEM", + p.add_argument("--outform", metavar="FMT", default="PEM", help="Select output format: PEM|DER. Default: PEM") @@ -529,30 +529,30 @@ def opts_reset(p: GParser) -> None: def opts_request(p: GParser) -> None: - p.add_argument("--request", metavar="FN", required=True, + p.add_argument("--request", metavar="CSR_FILE", required=True, help="Filename of certificate request (CSR) to be signed.") def opts_key(p: GParser) -> None: - p.add_argument("--key", metavar="FN", required=True, help="Private key file") + p.add_argument("--key", metavar="KEY_FILE", required=True, help="Private key file") def opts_ca_key(p: GParser) -> None: - p.add_argument("--ca-key", metavar="FN", required=True, - help="Private key file.") - p.add_argument("--ca-info", metavar="FN", required=True, + p.add_argument("--ca-info", metavar="CRT_FILE", required=True, help="Filename of CA details (CRT or CSR).") + p.add_argument("--ca-key", metavar="KEY_FILE", required=True, + help="CA private key file.") def opts_signing(p: GParser) -> None: + p.add_argument("--not-valid-before", metavar="DATE", + help="Start of validity period, default: (now - 1h)") + p.add_argument("--not-valid-after", metavar="DATE", + help="End of validity period, default: (now + DAYS)") p.add_argument("--days", type=int, help="Certificate lifetime in days from now") - p.add_argument("--not-valid-before", - help="Timestamp of validity period start") - p.add_argument("--not-valid-after", - help="Timestamp of validity period end") p.add_argument("--serial-number", metavar="SN", - help="Disable automatic serial number generation.") + help="Use SN instead automatically generated serial number.") def opts_rsa_pss(p: GParser) -> None: @@ -561,49 +561,44 @@ def opts_rsa_pss(p: GParser) -> None: def opts_cert_fields(p: GParser) -> None: - p.add_argument("--subject", + p.add_argument("--subject", metavar="DN", help="Subject Distinguished Name - /CN=foo/O=Org/OU=Web/") p.add_argument("--san", metavar="GNAMES", - help="SubjectAltNames - dns:hostname, email:addrspec, ip:ipaddr, uri:url, dn:DirName.") + help=("SubjectAltNames - dns:hostname, email:addrspec, ip:ipaddr," + " uri:url, dn:distinguishedName.")) + p.add_argument("--usage", + help="Keywords: client, server, code, email, time, ocsp.") p.add_argument("--CA", action="store_true", help="Request CA cert. Default: not set.") p.add_argument("--path-length", type=int, default=None, metavar="DEPTH", - help="Max levels of sub-CAs. Default: 0") - p.add_argument("--usage", - help="Keywords: client, server, code, email, time, ocsp.") + help="Max levels of sub-CAs. Default: not set") + p.add_argument("--crl-urls", metavar="URLS", + help="URLs URL for CRL data.") + p.add_argument("--issuer-urls", metavar="URLS", + help="URLs for issuer cert.") p.add_argument("--ocsp-urls", metavar="URLS", help="URLs for OCSP info.") - p.add_argument("--ocsp-nocheck", action="store_true", - help="Disable OCSP check.") p.add_argument("--ocsp-must-staple", action="store_true", help="OCSP Must-Staple.") p.add_argument("--ocsp-must-staple-v2", action="store_true", help="OCSP Must-Staple V2.") - p.add_argument("--crl-urls", metavar="URLS", - help="URLs URL for CRL data.") - p.add_argument("--issuer-urls", metavar="URLS", - help="URLs for issuer cert.") + p.add_argument("--ocsp-nocheck", action="store_true", + help="Disable OCSP check.") p.add_argument("--permit-subtrees", metavar="GNAMES", help="Allowed NameConstraints.") p.add_argument("--exclude-subtrees", metavar="GNAMES", help="Disallowed NameConstraints.") - p.add_argument("--inhibit-any", metavar="N", type=int, - help="Number of levels after which policy is ignored.") p.add_argument("--require-explicit-policy", metavar="N", type=int, help="Number of levels after which certificate policy is required.") p.add_argument("--inhibit-policy-mapping", metavar="N", type=int, help="Number of levels after which policy mapping is disallowed.") + p.add_argument("--inhibit-any", metavar="N", type=int, + help="Number of levels after which policy is ignored.") p.add_argument("--add-policy", metavar="POLICY", type=str, action="append", help="Add policy. Value is OID:/T=qualifier1/,/T=qualifier2/") -def opts_crl(p: GParser) -> None: - p.add_argument("--crl", metavar="FN", - help="Filename of certificate revocation list (CRL) to be updated.") - p.add_argument("--crl-number", metavar="VER", - help="Version number for main CRL") - p.add_argument("--delta-crl-number", metavar="VER", - help="Version number for parent CRL") +def opts_crl_entry(p: GParser) -> None: p.add_argument("--revoke-certs", metavar="FN", nargs="+", help="Certificate files to add") p.add_argument("--revoke-serials", metavar="NUM", nargs="+", @@ -611,19 +606,28 @@ def opts_crl(p: GParser) -> None: p.add_argument("--reason", help="Reason for revocation: %s" % ", ".join(CRL_REASON.keys())) p.add_argument("--invalidity-date", metavar="DATE", - help="Consider certificate invalid from date") + help="Consider certificate invalid from DATE") p.add_argument("--revocation-date", metavar="DATE", - help="Disable default timestamp") + help="Use DATE instead current timestamp") + + +def opts_crl(p: GParser) -> None: + p.add_argument("--crl", metavar="CRL_FILE", + help="Filename of certificate revocation list (CRL) to be updated.") + p.add_argument("--crl-number", metavar="VER", + help="Version number for main CRL") + p.add_argument("--delta-crl-number", metavar="VER", + help="Version number for parent CRL") p.add_argument("--crl-scope", metavar="SCOPE", help="Score for types of certificates in CRL, one of: all, user, ca, attr. Default: all") p.add_argument("--crl-reasons", metavar="REASONS", help="Limit CRL scope to only list of reasons") + p.add_argument("--indirect-crl", action="store_true", + help="Set Indirect-CRL flag") p.add_argument("--issuer-urls", metavar="URLS", # DBL help="URLs for issuer cert.") p.add_argument("--delta-crl-urls", metavar="URLS", help="Delta CRL URLs") - p.add_argument("--indirect-crl", action="store_true", - help="Set Indirect-CRL flag") p.add_argument("--last-update", metavar="DATE", help="Set last_update explicitly instead using current timestamp.") p.add_argument("--next-update", metavar="DATE", @@ -646,21 +650,31 @@ def opts_file(p: GParser) -> None: def opts_files(p: GParser) -> None: p.add_argument("file", help="File(s) to show", nargs="+") + # # collect per-command switches # +class CustomFormatter(argparse.HelpFormatter): + def __init__(self, prog: str) -> None: + super().__init__(prog, max_help_position=26) + class HelpArgs(TypedDict): help: str description: str + formatter_class: Type[argparse.HelpFormatter] def loadhelp(func: Callable[[SubParser], None]) -> HelpArgs: """Convert docstring to add_parser() args """ doc = (func.__doc__ or "").strip() - return {"help": doc, "description": doc} + return { + "help": doc, + "description": doc, + "formatter_class": CustomFormatter, + } def setup_args_newkey(sub: SubParser) -> None: @@ -757,6 +771,9 @@ def setup_args_update_crl(sub: SubParser) -> None: g = p.add_argument_group("CRL fields") opts_crl(g) + g = p.add_argument_group("Add revoked certificates") + opts_crl_entry(g) + def setup_args_export(sub: SubParser) -> None: """Reformat file @@ -836,12 +853,12 @@ def setup_args_autogen(sub: SubParser) -> None: def setup_args() -> argparse.ArgumentParser: """Create ArgumentParser """ - top = argparse.ArgumentParser( prog="sysca", description="Run any COMMAND with --help switch to get command-specific help.", fromfile_prefix_chars="@", allow_abbrev=False, + formatter_class=CustomFormatter, ) opts_top(top) opts_unsafe(top)