fix: support es256 raw format signature (#490)
es256 signature in id_token has raw format, however, cryptography library verification/signing only works for asn1 encoded format. Therefore in verification/signing process, we need to convert between the ans1 encoded format and the raw format.
diff --git a/google/auth/crypt/es256.py b/google/auth/crypt/es256.py
index 5bfd57f..6955efc 100644
--- a/google/auth/crypt/es256.py
+++ b/google/auth/crypt/es256.py
@@ -15,12 +15,15 @@
"""ECDSA (ES256) verifier and signer that use the ``cryptography`` library.
"""
+from cryptography import utils
import cryptography.exceptions
from cryptography.hazmat import backends
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import padding
+from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
+from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
import cryptography.x509
import pkg_resources
@@ -58,9 +61,17 @@
@_helpers.copy_docstring(base.Verifier)
def verify(self, message, signature):
+ # First convert (r||s) raw signature to ASN1 encoded signature.
+ sig_bytes = _helpers.to_bytes(signature)
+ if len(sig_bytes) != 64:
+ return False
+ r = utils.int_from_bytes(sig_bytes[:32], byteorder="big")
+ s = utils.int_from_bytes(sig_bytes[32:], byteorder="big")
+ asn1_sig = encode_dss_signature(r, s)
+
message = _helpers.to_bytes(message)
try:
- self._pubkey.verify(signature, message, ec.ECDSA(hashes.SHA256()))
+ self._pubkey.verify(asn1_sig, message, ec.ECDSA(hashes.SHA256()))
return True
except (ValueError, cryptography.exceptions.InvalidSignature):
return False
@@ -118,7 +129,11 @@
@_helpers.copy_docstring(base.Signer)
def sign(self, message):
message = _helpers.to_bytes(message)
- return self._key.sign(message, ec.ECDSA(hashes.SHA256()))
+ asn1_signature = self._key.sign(message, ec.ECDSA(hashes.SHA256()))
+
+ # Convert ASN1 encoded signature to (r||s) raw signature.
+ (r, s) = decode_dss_signature(asn1_signature)
+ return utils.int_to_bytes(r, 32) + utils.int_to_bytes(s, 32)
@classmethod
def from_string(cls, key, key_id=None):
diff --git a/tests/crypt/test_es256.py b/tests/crypt/test_es256.py
index 087ce6e..5bb9050 100644
--- a/tests/crypt/test_es256.py
+++ b/tests/crypt/test_es256.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import base64
import json
import os
@@ -72,6 +73,17 @@
bad_signature2 = b"a"
assert not verifier.verify(b"foo", bad_signature2)
+ def test_verify_failure_with_wrong_raw_signature(self):
+ to_sign = b"foo"
+
+ # This signature has a wrong "r" value in the "(r,s)" raw signature.
+ wrong_signature = base64.urlsafe_b64decode(
+ b"m7oaRxUDeYqjZ8qiMwo0PZLTMZWKJLFQREpqce1StMIa_yXQQ-C5WgeIRHW7OqlYSDL0XbUrj_uAw9i-QhfOJQ=="
+ )
+
+ verifier = es256.ES256Verifier.from_string(PUBLIC_KEY_BYTES)
+ assert not verifier.verify(to_sign, wrong_signature)
+
def test_from_string_pub_key(self):
verifier = es256.ES256Verifier.from_string(PUBLIC_KEY_BYTES)
assert isinstance(verifier, es256.ES256Verifier)