Skip to content

Commit

Permalink
adding check for newlines at end of signed message
Browse files Browse the repository at this point in the history
  • Loading branch information
DigitalTrustCenter committed Oct 28, 2024
1 parent ab460e3 commit 8e68200
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 38 deletions.
65 changes: 33 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,38 +44,39 @@ a dict with three keys:

### Possible errors

| code | message |
|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| "no_security_txt" | "security.txt could not be located." |
| "location" | "security.txt was located on the top-level path (legacy place), but must be placed under the '/.well-known/' path." |
| "invalid_uri_scheme" | "Insecure URI scheme HTTP is not allowed. The security.txt file access must use the HTTPS scheme" |
| "invalid_cert" | "security.txt must be served with a valid TLS certificate." |
| "no_content_type" | "HTTP Content-Type header must be sent." |
| "invalid_media" | "Media type in Content-Type header must be 'text/plain'." |
| "invalid_charset" | "Charset parameter in Content-Type header must be 'utf-8' if present." |
| "utf8" | "Content must be utf-8 encoded." |
| "no_expire" | "'Expires' field must be present." |
| "multi_expire" | "'Expires' field must not appear more than once." |
| "invalid_expiry" | "Date and time in 'Expires' field must be formatted according to ISO 8601." |
| "expired" | "Date and time in 'Expires' field must not be in the past." |
| "no_contact" | "'Contact' field must appear at least once." |
| "no_canonical_match" | "Web URI where security.txt is located must match with a 'Canonical' field. In case of redirecting either the first or last web URI of the redirect chain must match." |
| "multi_lang" | "'Preferred-Languages' field must not appear more than once." |
| "invalid_lang" | "Value in 'Preferred-Languages' field must match one or more language tags as defined in RFC5646, separated by commas." |
| "no_uri" | "Field '{field}' value must be a URI." |
| "no_https" | "Web URI must begin with 'https://'." |
| "prec_ws" | "There must be no whitespace before the field separator (colon)." |
| "no_space" | "Field separator (colon) must be followed by a space." |
| "empty_key" | "Field name must not be empty." |
| "empty_value" | "Field value must not be empty." |
| "invalid_line" | "Line must contain a field name and value, unless the line is blank or contains a comment." |
| "no_line_separators" | "Every line, including the last one, must end with either a carriage return and line feed characters or just a line feed character" |
| "signed_format_issue" | "Signed security.txt must start with the header '-----BEGIN PGP SIGNED MESSAGE-----'. " |
| "data_after_sig" | "Signed security.txt must not contain data after the signature." |
| "no_csaf_file" | "All CSAF fields must point to a provider-metadata.json file." |
| "pgp_data_error" | "Signed message did not contain a correct ASCII-armored PGP block." |
| "pgp_error" | "Decoding or parsing of the pgp message failed." |
| "bom_in_file" | "The Byte-Order Mark was found at the start of the file. Security.txt must be encoded using UTF-8 in Net-Unicode form, the BOM signature must not appear at the beginning." |
| code | message |
|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| "no_security_txt" | "security.txt could not be located." |
| "location" | "security.txt was located on the top-level path (legacy place), but must be placed under the '/.well-known/' path." |
| "invalid_uri_scheme" | "Insecure URI scheme HTTP is not allowed. The security.txt file access must use the HTTPS scheme" |
| "invalid_cert" | "security.txt must be served with a valid TLS certificate." |
| "no_content_type" | "HTTP Content-Type header must be sent." |
| "invalid_media" | "Media type in Content-Type header must be 'text/plain'." |
| "invalid_charset" | "Charset parameter in Content-Type header must be 'utf-8' if present." |
| "utf8" | "Content must be utf-8 encoded." |
| "no_expire" | "'Expires' field must be present." |
| "multi_expire" | "'Expires' field must not appear more than once." |
| "invalid_expiry" | "Date and time in 'Expires' field must be formatted according to ISO 8601." |
| "expired" | "Date and time in 'Expires' field must not be in the past." |
| "no_contact" | "'Contact' field must appear at least once." |
| "no_canonical_match" | "Web URI where security.txt is located must match with a 'Canonical' field. In case of redirecting either the first or last web URI of the redirect chain must match." |
| "multi_lang" | "'Preferred-Languages' field must not appear more than once." |
| "invalid_lang" | "Value in 'Preferred-Languages' field must match one or more language tags as defined in RFC5646, separated by commas." |
| "no_uri" | "Field '{field}' value must be a URI." |
| "no_https" | "Web URI must begin with 'https://'." |
| "prec_ws" | "There must be no whitespace before the field separator (colon)." |
| "no_space" | "Field separator (colon) must be followed by a space." |
| "empty_key" | "Field name must not be empty." |
| "empty_value" | "Field value must not be empty." |
| "invalid_line" | "Line must contain a field name and value, unless the line is blank or contains a comment." |
| "no_line_separators" | "Every line, including the last one, must end with either a carriage return and line feed characters or just a line feed character" |
| "signed_format_issue" | "Signed security.txt must start with the header '-----BEGIN PGP SIGNED MESSAGE-----'. " |
| "data_after_sig" | "Signed security.txt must not contain data after the signature." |
| "no_csaf_file" | "All CSAF fields must point to a provider-metadata.json file." |
| "pgp_data_error" | "Signed message did not contain a correct ASCII-armored PGP block." |
| "pgp_error" | "Decoding or parsing of the pgp message failed." |
| "bom_in_file" | "The Byte-Order Mark was found at the start of the file. Security.txt must be encoded using UTF-8 in Net-Unicode form, the BOM signature must not appear at the beginning." |
| "too_many_line_separators" | "A PGP signed message cannot end with more than one newline" |


### Possible recommendations
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ python-dateutil==2.9.0.post0
langcodes==3.3.0
pytest==8.1.1
requests-mock==1.12.1
PGPy@https://github.com/SecurityInnovation/PGPy/archive/09014c72b4557dd1254cf68a32e50f78515f5f32.zip
PGPy-dtc==0.1.0
validators==0.32.0
17 changes: 13 additions & 4 deletions sectxt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
from datetime import datetime, timezone
from typing import Optional, Union, List, DefaultDict
from urllib.parse import urlsplit, urlunsplit
import pgpy
from pgpy_dtc import PGPMessage
from pgpy_dtc.errors import PGPError
from dateutil.relativedelta import relativedelta
from pgpy.errors import PGPError

if sys.version_info < (3, 8):
from typing_extensions import TypedDict
Expand All @@ -24,7 +24,7 @@
import dateutil.parser
import requests

__version__ = "0.9.4"
__version__ = "0.9.5"

s = requests.Session()

Expand Down Expand Up @@ -162,7 +162,7 @@ def _parse_line(self, line: str) -> LineDict:

# Check pgp formatting if signed
try:
pgpy.PGPMessage.from_blob(self._content_str)
PGPMessage.from_blob(self._content_str)
except ValueError:
self._add_error(
"pgp_data_error",
Expand All @@ -173,6 +173,9 @@ def _parse_line(self, line: str) -> LineDict:
"pgp_error",
"Decoding or parsing of the pgp message failed."
)
except NotImplementedError as e:
# ignore this error for now since it does not indicate an issue with the pgp block
pass

return {"type": "pgp_envelope", "field_name": None, "value": line}

Expand Down Expand Up @@ -319,6 +322,12 @@ def validate_contents(self) -> None:
"or just a line feed character",
len(self.lines)
)
if self._signed and self.lines[-1]["type"] == "empty" and self.lines[-2]["type"] == "empty":
self._add_error(
"too_many_line_separators",
"A PGP signed message cannot end with more than one newline",
len(self.lines)
)

if "csaf" in self._values:
if not all(
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ install_requires =
requests==2.31.0
python-dateutil==2.9.0.post0
langcodes==3.3.0
PGPy
PGPy-dtc==0.1.0
validators==0.32.0
8 changes: 8 additions & 0 deletions test/test_sectxt.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,14 @@ def test_byte_order_mark(self):
if not any(d["code"] == "bom_in_file" for d in s.errors):
pytest.fail("bom_in_file error code should be given")

def test_too_many_final_newlines_signed(self):
content = _signed_example + "\n"
p = Parser(content.encode())
self.assertFalse(p.is_valid())
self.assertEqual(
len([1 for r in p._errors if r["code"] == "too_many_line_separators"]), 1
)

# noinspection PyMethodMayBeStatic
def test_local_file(self):
# Create a text file to be used for the local test
Expand Down

0 comments on commit 8e68200

Please sign in to comment.