Repository: pyca/cryptography

Issue:
RFC4514 parser doesn't accept OID overrides
With v37.0 came RFC4514 string parsing support, which is great 🎉 

But it seems it's lacking support for adding extra OIDs.
For example, subjects wit an email address in them.

```python
>>> from cryptography import x509
>>> from cryptography.x509 import NameOID
>>> name = x509.Name([
...     x509.NameAttribute(NameOID.EMAIL_ADDRESS, "santa@north.pole"),
...     x509.NameAttribute(NameOID.COMMON_NAME, "Santa Claus"),
... ])

>>> name
<Name(1.2.840.113549.1.9.1=santa@north.pole,CN=Santa Claus)>

>>> name.rfc4514_string()
'CN=Santa Claus,1.2.840.113549.1.9.1=santa@north.pole'

>>> x509.Name.from_rfc4514_string(name.rfc4514_string())
<Name(CN=Santa Claus,1.2.840.113549.1.9.1=santa@north.pole)>

>>> name.rfc4514_string({NameOID.EMAIL_ADDRESS: "E"})
'CN=Santa Claus,E=santa@north.pole'

>>> x509.Name.from_rfc4514_string(name.rfc4514_string({NameOID.EMAIL_ADDRESS: "E"}))
Traceback (most recent call last):
  File ".venv\lib\site-packages\cryptography\x509\name.py", line 428, in _parse_na
    oid_value = self._read_re(self._OID_RE)
  File ".venv\lib\site-packages\cryptography\x509\name.py", line 404, in _read_re
    raise ValueError
ValueError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "\Python310\lib\code.py", line 90, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>
  File ".venv\lib\site-packages\cryptography\x509\name.py", line 301, in from_rfc4514_string
    return _RFC4514NameParser(data).parse()
  File ".venv\lib\site-packages\cryptography\x509\name.py", line 414, in parse
    rdns.append(self._parse_rdn())
  File ".venv\lib\site-packages\cryptography\x509\name.py", line 419, in _parse_rdn
    nas = [self._parse_na()]
  File ".venv\lib\site-packages\cryptography\x509\name.py", line 433, in _parse_na
    raise ValueError
ValueError
```



Discussion:

Resolving patch:
diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py
--- a/src/cryptography/x509/name.py
+++ b/src/cryptography/x509/name.py
@@ -42,6 +42,7 @@ class _ASN1Type(utils.Enum):
 
 # Type alias
 _OidNameMap = typing.Mapping[ObjectIdentifier, str]
+_NameOidMap = typing.Mapping[str, ObjectIdentifier]
 
 #: Short attribute names from RFC 4514:
 #: https://tools.ietf.org/html/rfc4514#page-7
@@ -297,8 +298,12 @@ def __init__(
             )
 
     @classmethod
-    def from_rfc4514_string(cls, data: str) -> "Name":
-        return _RFC4514NameParser(data).parse()
+    def from_rfc4514_string(
+        cls,
+        data: str,
+        attr_name_overrides: typing.Optional[_NameOidMap] = None,
+    ) -> "Name":
+        return _RFC4514NameParser(data, attr_name_overrides or {}).parse()
 
     def rfc4514_string(
         self, attr_name_overrides: typing.Optional[_OidNameMap] = None
@@ -381,10 +386,12 @@ class _RFC4514NameParser:
     )
     _HEXSTRING_RE = re.compile(r"#([\da-zA-Z]{2})+")
 
-    def __init__(self, data: str) -> None:
+    def __init__(self, data: str, attr_name_overrides: _NameOidMap) -> None:
         self._data = data
         self._idx = 0
 
+        self._attr_name_overrides = attr_name_overrides
+
     def _has_data(self) -> bool:
         return self._idx < len(self._data)
 
@@ -428,7 +435,9 @@ def _parse_na(self) -> NameAttribute:
             oid_value = self._read_re(self._OID_RE)
         except ValueError:
             name = self._read_re(self._DESCR_RE)
-            oid = _NAME_TO_NAMEOID.get(name)
+            oid = self._attr_name_overrides.get(
+                name, _NAME_TO_NAMEOID.get(name)
+            )
             if oid is None:
                 raise ValueError
         else:


Test patch:
diff --git a/tests/x509/test_name.py b/tests/x509/test_name.py
--- a/tests/x509/test_name.py
+++ b/tests/x509/test_name.py
@@ -164,3 +164,21 @@ def test_valid(self, subtests):
             with subtests.test():
                 result = Name.from_rfc4514_string(value)
                 assert result == expected
+
+    def test_attr_name_override(self):
+        assert Name.from_rfc4514_string(
+            "CN=Santa Claus,E=santa@north.pole", {"E": NameOID.EMAIL_ADDRESS}
+        ) == Name(
+            [
+                NameAttribute(NameOID.COMMON_NAME, "Santa Claus"),
+                NameAttribute(NameOID.EMAIL_ADDRESS, "santa@north.pole"),
+            ]
+        )
+
+        assert Name.from_rfc4514_string(
+            "CN=Santa Claus", {"CN": NameOID.EMAIL_ADDRESS}
+        ) == Name(
+            [
+                NameAttribute(NameOID.EMAIL_ADDRESS, "Santa Claus"),
+            ]
+        )