Repository: pyca/cryptography

Issue:
Retrieving the signature algorithm name
When looking into the details of a certificate, I'm used to see a signature algorithm name like `sha256WithRSAEncryption` with `openssl x509 -noout -text` or `SHA-256 with RSA Encryption` when looking at it with Chrome.  Both versions are just a representation of the OID `1.2.840.113549.1.1.11`, i.e. a combination of the signing algorithm and the hash algorithm.

With the `x509.signature_hash_algorithm` property I can access the hash algorithm of the certificate, yet I can't find an easy way to access the signing algorithm as well.

The `x509.oid.SignatureAlgorithmOID` class defines several constants, e.g.

``` python
RSA_WITH_SHA256 = ObjectIdentifier("1.2.840.113549.1.1.11")
```

and the `x509.oid` module defines a dictionary of `_OID_NAMES`, e.g.

``` python
SignatureAlgorithmOID.RSA_WITH_SHA256: "sha256WithRSAEncryption"
```

So basically, this information is incorporated into the cryptography library already.

Due to the nature of the dictionary `x509.oid._SIG_OIDS_TO_HASH`, e.g.

``` python
SignatureAlgorithmOID.RSA_WITH_SHA256.dotted_string: hashes.SHA256(),
SignatureAlgorithmOID.ECDSA_WITH_SHA256.dotted_string: hashes.SHA256(),
SignatureAlgorithmOID.DSA_WITH_SHA256.dotted_string: hashes.SHA256()
```

the signing algorithm gets lost, so `x509.signature_hash_algorithm` only returns to the hash algorithm (as the property name suggests). Once I retrieved the signature algorithm OID on my own, I can retrieve the name with

``` python
x509.oid._OID_NAMES[x509.oid.ObjectIdentifier('1.2.840.113549.1.1.11')]
```

as well. So if there was a function similar to `x509.signature_hash_algorithm` returning

``` python
x509.oid._OID_NAMES[oid]
```

rather than

``` python
x509._SIG_OIDS_TO_HASH[oid]
```

I'd be happy. :) I don't want to mess up the currently very clean interface though, so maybe there's a simpler way to retrieve the same information that I missed somehow?



Discussion:
You can obtain the sum of that data by looking at the type of the public key object + `signature_hash_algorithm` right now. Is there a specific reason you need the OID itself? I suppose the requirement to do `isinstance(cert.public_key(), RSAPublicKey)` is itself inelegant.

Err, that's not correct, you need the issue's public key, rigth?

On Tue, Jun 21, 2016 at 9:13 AM, Paul Kehrer notifications@github.com
wrote:

> You can obtain the sum of that data by looking at the type of the public
> key object + signature_hash_algorithm right now. Is there a specific
> reason you need the OID itself? I suppose the requirement to do isinstance(cert.public_key(),
> RSAPublicKey) is itself inelegant.
> 
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> https://github.com/pyca/cryptography/issues/3009#issuecomment-227435093,
> or mute the thread
> https://github.com/notifications/unsubscribe/AAADBG68mmv3m95hgIHWSWM-BI5NPznUks5qN-N2gaJpZM4I6tT8
> .

## 

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: D1B3 ADC0 E023 8CA6

You're not going to find mixed chains in the wild (having said that someone will prove me wrong...and alex did in IRC), but we should do better. When we originally designed this API we tried to explicitly avoid returning stringly typed information like `sha256WithRSAEncryption`.

We could return the OID object itself of course (e.g. `cert.signature_oid`), but maybe we need an analog to `signature_hash_algorithm` like `signature_key_type` which returns...what. The only thing I can think of off the top of my head is the class of key that did the signing (e.g. `EllipticCurvePublicKey` or `RSAPublicKey`) but that's not great.

Maybe we start with just exposing the OID.

We may want to think a bit about what this API looks like in a theoretical
world where people actually issue RSA-PSS certs?

On Tue, Jun 21, 2016 at 9:23 AM, Paul Kehrer notifications@github.com
wrote:

> You're not going to find mixed chains in the wild (having said that
> someone will prove me wrong...and alex did in IRC), but we should do
> better. When we originally designed this API we tried to explicitly avoid
> returning stringly typed information like sha256WithRSAEncryption. We
> could return the OID object itself of course (e.g. cert.signature_oid),
> but maybe we need an analog to signature_hash_algorithm like
> signature_key_type which returns...what. The only thing I can think of
> off the top of my head is the class of key that did the signing (e.g.
> EllipticCurvePublicKey or RSAPublicKey) but that's not great. Maybe we
> start with just exposing the OID.
> 
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> https://github.com/pyca/cryptography/issues/3009#issuecomment-227437782,
> or mute the thread
> https://github.com/notifications/unsubscribe/AAADBAzgJ92TyhovTGpr0DxQqtMus0Yjks5qN-XIgaJpZM4I6tT8
> .

## 

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: D1B3 ADC0 E023 8CA6

If we just expose the OID we can sidestep that issue for the moment since RSA-PSS is `1.2.840.113549.1.1.10` and then there's a separate sequence defining the PSS params. I'd rather solve this issue and then tackle PSS params as a separate one (when the time comes). I think a PR for `signature_algorithm_oid` covers this.


Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py
--- a/src/cryptography/hazmat/backends/openssl/x509.py
+++ b/src/cryptography/hazmat/backends/openssl/x509.py
@@ -107,12 +107,7 @@ def subject(self):
 
     @property
     def signature_hash_algorithm(self):
-        alg = self._backend._ffi.new("X509_ALGOR **")
-        self._backend._lib.X509_get0_signature(
-            self._backend._ffi.NULL, alg, self._x509
-        )
-        self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL)
-        oid = _obj2txt(self._backend, alg[0].algorithm)
+        oid = self.signature_algorithm_oid
         try:
             return x509._SIG_OIDS_TO_HASH[oid]
         except KeyError:
@@ -120,6 +115,16 @@ def signature_hash_algorithm(self):
                 "Signature algorithm OID:{0} not recognized".format(oid)
             )
 
+    @property
+    def signature_algorithm_oid(self):
+        alg = self._backend._ffi.new("X509_ALGOR **")
+        self._backend._lib.X509_get0_signature(
+            self._backend._ffi.NULL, alg, self._x509
+        )
+        self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL)
+        oid = _obj2txt(self._backend, alg[0].algorithm)
+        return x509.ObjectIdentifier(oid)
+
     @property
     def extensions(self):
         return _CERTIFICATE_EXTENSION_PARSER.parse(self._backend, self._x509)
@@ -223,12 +228,7 @@ def fingerprint(self, algorithm):
 
     @property
     def signature_hash_algorithm(self):
-        alg = self._backend._ffi.new("X509_ALGOR **")
-        self._backend._lib.X509_CRL_get0_signature(
-            self._x509_crl, self._backend._ffi.NULL, alg
-        )
-        self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL)
-        oid = _obj2txt(self._backend, alg[0].algorithm)
+        oid = self.signature_algorithm_oid
         try:
             return x509._SIG_OIDS_TO_HASH[oid]
         except KeyError:
@@ -236,6 +236,16 @@ def signature_hash_algorithm(self):
                 "Signature algorithm OID:{0} not recognized".format(oid)
             )
 
+    @property
+    def signature_algorithm_oid(self):
+        alg = self._backend._ffi.new("X509_ALGOR **")
+        self._backend._lib.X509_CRL_get0_signature(
+            self._x509_crl, self._backend._ffi.NULL, alg
+        )
+        self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL)
+        oid = _obj2txt(self._backend, alg[0].algorithm)
+        return x509.ObjectIdentifier(oid)
+
     @property
     def issuer(self):
         issuer = self._backend._lib.X509_CRL_get_issuer(self._x509_crl)
@@ -355,12 +365,7 @@ def subject(self):
 
     @property
     def signature_hash_algorithm(self):
-        alg = self._backend._ffi.new("X509_ALGOR **")
-        self._backend._lib.X509_REQ_get0_signature(
-            self._x509_req, self._backend._ffi.NULL, alg
-        )
-        self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL)
-        oid = _obj2txt(self._backend, alg[0].algorithm)
+        oid = self.signature_algorithm_oid
         try:
             return x509._SIG_OIDS_TO_HASH[oid]
         except KeyError:
@@ -368,6 +373,16 @@ def signature_hash_algorithm(self):
                 "Signature algorithm OID:{0} not recognized".format(oid)
             )
 
+    @property
+    def signature_algorithm_oid(self):
+        alg = self._backend._ffi.new("X509_ALGOR **")
+        self._backend._lib.X509_REQ_get0_signature(
+            self._x509_req, self._backend._ffi.NULL, alg
+        )
+        self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL)
+        oid = _obj2txt(self._backend, alg[0].algorithm)
+        return x509.ObjectIdentifier(oid)
+
     @property
     def extensions(self):
         x509_exts = self._backend._lib.X509_REQ_get_extensions(self._x509_req)
diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py
--- a/src/cryptography/x509/base.py
+++ b/src/cryptography/x509/base.py
@@ -125,6 +125,12 @@ def signature_hash_algorithm(self):
         in the certificate.
         """
 
+    @abc.abstractproperty
+    def signature_algorithm_oid(self):
+        """
+        Returns the ObjectIdentifier of the signature algorithm.
+        """
+
     @abc.abstractproperty
     def extensions(self):
         """
@@ -189,6 +195,12 @@ def signature_hash_algorithm(self):
         in the certificate.
         """
 
+    @abc.abstractproperty
+    def signature_algorithm_oid(self):
+        """
+        Returns the ObjectIdentifier of the signature algorithm.
+        """
+
     @abc.abstractproperty
     def issuer(self):
         """
@@ -277,6 +289,12 @@ def signature_hash_algorithm(self):
         in the certificate.
         """
 
+    @abc.abstractproperty
+    def signature_algorithm_oid(self):
+        """
+        Returns the ObjectIdentifier of the signature algorithm.
+        """
+
     @abc.abstractproperty
     def extensions(self):
         """
diff --git a/src/cryptography/x509/oid.py b/src/cryptography/x509/oid.py
--- a/src/cryptography/x509/oid.py
+++ b/src/cryptography/x509/oid.py
@@ -135,20 +135,20 @@ class SignatureAlgorithmOID(object):
     DSA_WITH_SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.3.2")
 
 _SIG_OIDS_TO_HASH = {
-    SignatureAlgorithmOID.RSA_WITH_MD5.dotted_string: hashes.MD5(),
-    SignatureAlgorithmOID.RSA_WITH_SHA1.dotted_string: hashes.SHA1(),
-    SignatureAlgorithmOID.RSA_WITH_SHA224.dotted_string: hashes.SHA224(),
-    SignatureAlgorithmOID.RSA_WITH_SHA256.dotted_string: hashes.SHA256(),
-    SignatureAlgorithmOID.RSA_WITH_SHA384.dotted_string: hashes.SHA384(),
-    SignatureAlgorithmOID.RSA_WITH_SHA512.dotted_string: hashes.SHA512(),
-    SignatureAlgorithmOID.ECDSA_WITH_SHA1.dotted_string: hashes.SHA1(),
-    SignatureAlgorithmOID.ECDSA_WITH_SHA224.dotted_string: hashes.SHA224(),
-    SignatureAlgorithmOID.ECDSA_WITH_SHA256.dotted_string: hashes.SHA256(),
-    SignatureAlgorithmOID.ECDSA_WITH_SHA384.dotted_string: hashes.SHA384(),
-    SignatureAlgorithmOID.ECDSA_WITH_SHA512.dotted_string: hashes.SHA512(),
-    SignatureAlgorithmOID.DSA_WITH_SHA1.dotted_string: hashes.SHA1(),
-    SignatureAlgorithmOID.DSA_WITH_SHA224.dotted_string: hashes.SHA224(),
-    SignatureAlgorithmOID.DSA_WITH_SHA256.dotted_string: hashes.SHA256()
+    SignatureAlgorithmOID.RSA_WITH_MD5: hashes.MD5(),
+    SignatureAlgorithmOID.RSA_WITH_SHA1: hashes.SHA1(),
+    SignatureAlgorithmOID.RSA_WITH_SHA224: hashes.SHA224(),
+    SignatureAlgorithmOID.RSA_WITH_SHA256: hashes.SHA256(),
+    SignatureAlgorithmOID.RSA_WITH_SHA384: hashes.SHA384(),
+    SignatureAlgorithmOID.RSA_WITH_SHA512: hashes.SHA512(),
+    SignatureAlgorithmOID.ECDSA_WITH_SHA1: hashes.SHA1(),
+    SignatureAlgorithmOID.ECDSA_WITH_SHA224: hashes.SHA224(),
+    SignatureAlgorithmOID.ECDSA_WITH_SHA256: hashes.SHA256(),
+    SignatureAlgorithmOID.ECDSA_WITH_SHA384: hashes.SHA384(),
+    SignatureAlgorithmOID.ECDSA_WITH_SHA512: hashes.SHA512(),
+    SignatureAlgorithmOID.DSA_WITH_SHA1: hashes.SHA1(),
+    SignatureAlgorithmOID.DSA_WITH_SHA224: hashes.SHA224(),
+    SignatureAlgorithmOID.DSA_WITH_SHA256: hashes.SHA256()
 }
 
 


Test patch:
diff --git a/tests/test_x509.py b/tests/test_x509.py
--- a/tests/test_x509.py
+++ b/tests/test_x509.py
@@ -31,7 +31,8 @@
     decode_dss_signature
 )
 from cryptography.x509.oid import (
-    AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID, NameOID
+    AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID,
+    NameOID, SignatureAlgorithmOID
 )
 
 from .hazmat.primitives.fixtures_dsa import DSA_KEY_2048
@@ -75,6 +76,10 @@ def test_load_pem_crl(self, backend):
         fingerprint = binascii.hexlify(crl.fingerprint(hashes.SHA1()))
         assert fingerprint == b"3234b0cb4c0cedf6423724b736729dcfc9e441ef"
         assert isinstance(crl.signature_hash_algorithm, hashes.SHA256)
+        assert (
+            crl.signature_algorithm_oid ==
+            SignatureAlgorithmOID.RSA_WITH_SHA256
+        )
 
     def test_load_der_crl(self, backend):
         crl = _load_cert(
@@ -493,6 +498,9 @@ def test_load_pem_cert(self, backend):
         fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1()))
         assert fingerprint == b"2b619ed04bfc9c3b08eb677d272192286a0947a8"
         assert isinstance(cert.signature_hash_algorithm, hashes.SHA1)
+        assert (
+            cert.signature_algorithm_oid == SignatureAlgorithmOID.RSA_WITH_SHA1
+        )
 
     def test_cert_serial_number(self, backend):
         cert = _load_cert(
@@ -1053,6 +1061,10 @@ class TestRSACertificateRequest(object):
     def test_load_rsa_certificate_request(self, path, loader_func, backend):
         request = _load_cert(path, loader_func, backend)
         assert isinstance(request.signature_hash_algorithm, hashes.SHA1)
+        assert (
+            request.signature_algorithm_oid ==
+            SignatureAlgorithmOID.RSA_WITH_SHA1
+        )
         public_key = request.public_key()
         assert isinstance(public_key, rsa.RSAPublicKey)
         subject = request.subject