Implementazione open-source del protocollo di Strong Customer Authentication di Poste Italiane, (https://posteid.poste.it), lato client.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
3.5 KiB

2 years ago
  1. import json
  2. from base64 import b64encode, b64decode
  3. from pathlib import Path
  4. import xdgappdirs
  5. from cryptography.hazmat.primitives.asymmetric import rsa
  6. from cryptography.hazmat.primitives import serialization
  7. from jwcrypto import jwk
  8. from pathvalidate import sanitize_filename
  9. data_dir = xdgappdirs.user_data_dir('pyjod', as_path=True)
  10. class RSAPrivateKey:
  11. def __init__(self, key):
  12. self.key = key
  13. @classmethod
  14. def generate(cls):
  15. key = rsa.generate_private_key(65537, 2048)
  16. return cls(key)
  17. @classmethod
  18. def from_pem(cls, data):
  19. key = serialization.load_pem_private_key(data, None)
  20. return cls(key)
  21. @property
  22. def jwk(self):
  23. return jwk.JWK.from_pyca(self.key)
  24. @property
  25. def pubkey_b64(self):
  26. pubkey = self.key.public_key()
  27. pubkey_bytes = pubkey.public_bytes(
  28. serialization.Encoding.DER,
  29. serialization.PublicFormat.SubjectPublicKeyInfo
  30. )
  31. return b64encode(pubkey_bytes).decode('utf-8')
  32. def to_pem(self):
  33. return self.key.private_bytes(
  34. serialization.Encoding.PEM,
  35. serialization.PrivateFormat.TraditionalOpenSSL,
  36. serialization.NoEncryption()
  37. )
  38. class RSAPublicKey:
  39. def __init__(self, key):
  40. self.key = key
  41. @classmethod
  42. def from_b64(cls, data):
  43. key_bytes = b64decode(data)
  44. key = serialization.load_der_public_key(key_bytes)
  45. return cls(key)
  46. @classmethod
  47. def from_pem(cls, data):
  48. key = serialization.load_pem_public_key(data)
  49. return cls(key)
  50. @property
  51. def jwk(self):
  52. return jwk.JWK.from_pyca(self.key)
  53. def to_pem(self):
  54. return self.key.public_bytes(
  55. serialization.Encoding.PEM,
  56. serialization.PublicFormat.SubjectPublicKeyInfo
  57. )
  58. class AppData:
  59. def __init__(self, profile_name):
  60. self.profile_dir = data_dir / sanitize_filename(profile_name)
  61. self.profile_dir.mkdir(exist_ok=True, parents=True)
  62. self.values_file = self.profile_dir / "values.json"
  63. @property
  64. def app_privkey(self):
  65. key_file = self.profile_dir / 'app_privkey.pem'
  66. if key_file.is_file():
  67. with key_file.open('rb') as f:
  68. key_bytes = f.read()
  69. return RSAPrivateKey.from_pem(key_bytes)
  70. else:
  71. key = RSAPrivateKey.generate()
  72. with key_file.open('wb') as f:
  73. f.write(key.to_pem())
  74. return key
  75. @property
  76. def serv_pubkey(self):
  77. key_file = self.profile_dir / 'serv_pubkey.pem'
  78. if key_file.is_file():
  79. with key_file.open('rb') as f:
  80. key_bytes = f.read()
  81. return RSAPublicKey.from_pem(key_bytes)
  82. else:
  83. return None
  84. @serv_pubkey.setter
  85. def serv_pubkey(self, key_b64):
  86. key = RSAPublicKey.from_b64(key_b64)
  87. key_file = self.profile_dir / 'serv_pubkey.pem'
  88. with key_file.open('wb') as f:
  89. f.write(key.to_pem())
  90. def __getitem__(self, key):
  91. if self.values_file.is_file():
  92. with self.values_file.open() as f:
  93. values = json.load(f)
  94. if key in values:
  95. return values[key]
  96. return None
  97. def __setitem__(self, key, value):
  98. if self.values_file.is_file():
  99. with self.values_file.open() as f:
  100. values = json.load(f)
  101. else:
  102. values = {}
  103. values[key] = value
  104. with self.values_file.open('w') as f:
  105. json.dump(values, f)