def _compute_kek(self, target_alg: '_EncAlg', ops: 'str') -> bytes: if self.key is None: # try to derive from this recipients' recipient list if not len(self.recipients): raise CoseException(f"No key found to {ops} the CEK") else: r_types = CoseRecipient.verify_recipients(self.recipients) if ops == 'encrypt': if DirectKeyAgreement in r_types: self.key = self.recipients[0].compute_cek(target_alg) elif KeyWrap in r_types or KeyAgreementWithKeyWrap in r_types: key_bytes = os.urandom(self.get_attr( headers.Algorithm)) for r in self.recipients: r.payload = key_bytes self.key = SymmetricKey(k=key_bytes) else: raise CoseException('Unsupported COSE recipient class') else: if DirectKeyAgreement in r_types or KeyWrap in r_types or KeyAgreementWithKeyWrap in r_types: self.key = self.recipients[0].decrypt( self.get_attr(headers.Algorithm)) else: raise CoseException('Unsupported COSE recipient class') if self.key is None: raise CoseException("No key found to decrypt the CEK") return self.key.k
def encrypt(self, target_alg: '_EncAlg') -> bytes: alg = self.get_attr(headers.Algorithm) if A128KW.identifier >= alg.identifier >= A256KW.identifier: if len(self.phdr): raise CoseException( f"Protected header must be empty when using an AE algorithm: {alg}" ) if alg is None: raise CoseException( "The algorithm parameter should at least be included in the unprotected header" ) self.key = SymmetricKey(k=self._compute_kek(target_alg, ops='encrypt'), optional_params={ KpAlg: alg, KpKeyOps: [WrapOp, EncryptOp] }) self.key.verify(SymmetricKey, alg, [WrapOp, EncryptOp]) return alg.key_wrap(self.key, self.payload) else: raise CoseIllegalAlgorithm(f"Algorithm {alg} for {self.__name__}")
def encrypt(self, target_alg: '_EncAlg') -> bytes: alg = self.get_attr(headers.Algorithm) if len(self.phdr): raise CoseException( f"Protected header must be empty when using an AE algorithm: {alg}" ) if alg is None: raise CoseException( "The algorithm parameter should at least be included in the unprotected header" ) elif alg in {A128KW, A192KW, A256KW}: key_ops = [WrapOp, EncryptOp] kek = SymmetricKey(k=self._compute_kek(target_alg, ops='encrypt'), optional_params={ KpAlg: alg, KpKeyOps: key_ops }) kek.verify(SymmetricKey, alg, [WrapOp, EncryptOp]) elif alg in {RsaesOaepSha512, RsaesOaepSha256, RsaesOaepSha1}: kek = self.key kek.verify(RSAKey, alg, [WrapOp, EncryptOp]) else: raise CoseIllegalAlgorithm(f"Algorithm {alg} for {self.__name__}") return alg.key_wrap(kek, self.payload)
def _key_verification(self, alg: Type['CoseAlg'], ops: Type['KEYOPS']): if self.key is None: raise CoseException("Key cannot be None") if isinstance(self.key, EC2Key): self.key.verify(EC2Key, alg, [ops]) elif isinstance(self.key, OKPKey): self.key.verify(OKPKey, alg, [ops]) else: raise CoseException('Wrong key type')
def signers(self, signers: List['Signer']): if isinstance(signers, list): for s in signers: s._parent = self self._signers = signers else: raise CoseException("Signers must be of type list")
def get_attr(self, attribute: Type[CoseHeaderAttribute], default: Any = None) -> Optional[Any]: """ Fetches an header attribute from the COSE header buckets. :param attribute: A header parameter to fetch from the buckets. :param default: A default return value in case the attribute was not found :raise CoseException: When the same attribute is found in both the protected and unprotected header. :returns: If found returns a header attribute else 'None' or the default value """ p_attr = self._phdr.get(attribute, default) u_attr = self._uhdr.get(attribute, default) if p_attr is not None and u_attr is not None: raise CoseException( "MALFORMED: different values for the same header parameters in the header buckets" ) if p_attr is not None: return p_attr else: return u_attr
def compute_tag(self, *args, **kwargs) -> bytes: target_algorithm = self.get_attr(headers.Algorithm) r_types = CoseRecipient.verify_recipients(self.recipients) if DirectEncryption in r_types: # key should already be known payload = super(MacMessage, self).compute_tag() elif DirectKeyAgreement in r_types: self.key = self.recipients[0].compute_cek(target_algorithm, "encrypt") payload = super(MacMessage, self).compute_tag() elif KeyWrap in r_types or KeyAgreementWithKeyWrap in r_types: key_bytes = os.urandom( self.get_attr(headers.Algorithm).get_key_length()) for r in self.recipients: if r.payload == b'': r.payload = key_bytes else: key_bytes = r.payload r.encrypt(target_algorithm) self.key = SymmetricKey(k=key_bytes, alg=target_algorithm, key_ops=[MacCreateOp]) payload = super(MacMessage, self).compute_tag() else: raise CoseException('Unsupported COSE recipient class') return payload
def verify_recipients(cls, recipients: List['Recipient']) -> set: r_types = set() for r in recipients: r_types.add(r.__class__) if DirectEncryption in r_types and len(r_types) > 1: CoseException( 'When using DIRECT_ENCRYPTION mode, it must be the only mode used on the message' ) if DirectKeyAgreement in r_types and len(recipients) > 1: CoseException( 'When using DIRECT_KEY_AGREEMENT, it must be only one recipient in the message.' ) return r_types
def encode(self, *args, **kwargs) -> list: alg = self.get_attr(headers.Algorithm) if alg is None: raise CoseMalformedMessage( "The algorithm parameter should be included in either the protected header or " "unprotected header") # static receiver key peer_key: 'EC2Key' = self.local_attrs.get(headers.StaticKey) if peer_key is None: raise CoseException( "Static receiver key cannot be None. Should be configured in 'local_attrs' of the msg." ) # if ephemeral and not set, generate ephemeral key pair if self.key is None: if alg in {EcdhEsHKDF256, EcdhEsHKDF512}: self._setup_ephemeral_key(peer_key) else: # alg uses a static sender raise CoseException("Static sender key cannot be None") if len(self.recipients) > 1: raise CoseMalformedMessage( f'Recipient class DIRECT_KEY_AGREEMENT cannot carry more recipients' ) # only the ephemeral sender key MUST be included in the header, for the static sender it is recommended by not # obligated if self.get_attr(headers.EphemeralKey) is None and alg in { EcdhEsHKDF512, EcdhEsHKDF256 }: raise CoseMalformedMessage( f'Recipient class DIRECT_KEY_AGREEMENT must carry an ephemeral COSE key object' ) recipient = [self.phdr_encoded, self.uhdr_encoded, b''] if len(self.recipients): recipient.append( [r.encode(*args, **kwargs) for r in self.recipients]) return recipient
def encode(self, *args, **kwargs) -> list: alg = self.get_attr(headers.Algorithm) if alg == Direct and len(self.phdr) != 0: raise CoseException("Protected header must be empty") if alg is None: raise CoseException( "Message must carry an algorithm parameter when using DIRECT_ENCRYPTION mode" ) if len(self.recipients): raise CoseException( f"Recipient class DIRECT_ENCRYPTION cannot carry recipients.") recipient = [self.phdr_encoded, self.uhdr_encoded, b''] return recipient
def sign(cls, key: 'OKP', data: bytes) -> bytes: if key.crv.fullname == 'ED25519': sk = Ed25519PrivateKey.from_private_bytes(key.d) elif key.crv.fullname == 'ED448': sk = Ed448PrivateKey.from_private_bytes(key.d) else: raise CoseException(f"Illegal curve for OKP singing: {key.crv}") return sk.sign(data)
def key(self, key: Optional['CK']): if isinstance(key, SymmetricKey): if key.k == b'': raise CoseException("Key does not contain secret bytes") self._key = key elif isinstance(key, EC2Key): if key.d == b'' and key.x == b'' and key.y == b'': raise CoseException( "Key does not contain private bytes or public bytes") self._key = key elif isinstance(key, OKPKey): if key.d == b'' and key.x == b'': raise CoseException( "Key does not contain private bytes or public bytes") self._key = key elif key is None: self._key = key else: raise CoseException("Invalid COSE key")
def verify_tag(self, *args, **kwargs) -> bool: """ Verifies the authentication tag of a received message. """ alg = self.get_attr(headers.Algorithm) if self.key is None: raise CoseException("Key cannot be None") self.key.verify(SymmetricKey, alg, [MacVerifyOp]) return alg.verify_tag(key=self.key, tag=self.auth_tag, data=self._mac_structure)
def decrypt(self, recipient: 'Recipient', *args, **kwargs) -> bytes: target_algorithm = self.get_attr(headers.Algorithm) # check if recipient exists if not CoseRecipient.has_recipient(recipient, self.recipients): raise CoseException(f"Cannot find recipient: {recipient}") r_types = CoseRecipient.verify_recipients(self.recipients) if DirectEncryption in r_types: # key should already be known payload = super(EncMessage, self).decrypt() elif DirectKeyAgreement in r_types or KeyWrap in r_types or KeyAgreementWithKeyWrap in r_types: self.key = recipient.compute_cek(target_algorithm, "decrypt") payload = super(EncMessage, self).decrypt() else: raise CoseException('Unsupported COSE recipient class') return payload
def _get_nonce(self): nonce = self.get_attr(headers.IV) if nonce is None and self.key.base_iv != b'': partial_iv = self.get_attr(headers.PartialIV) nonce = int.from_bytes(partial_iv, "big") ^ int.from_bytes(self.key.base_iv, "big") nonce = nonce.to_bytes((nonce.bit_length() + 7) // 8, byteorder="big") if nonce is None and self.key.base_iv == b'': raise CoseException('No IV found') return nonce
def compute_tag(self, *args, **kwargs) -> bytes: """ Computes the authentication tag of a COSE_Mac or COSE_Mac0 message. """ alg = self.get_attr(headers.Algorithm) if self.key is None: raise CoseException("Key cannot be None") self.key.verify(SymmetricKey, alg, [MacCreateOp]) self.auth_tag = alg.compute_tag(key=self.key, data=self._mac_structure) return self.auth_tag
def create_recipient(cls, recipient: list, context: str): p_alg = cls._parse_header(cbor2.loads(recipient[0])).get( headers.Algorithm) if recipient[0] != b'' else None u_alg = cls._parse_header(recipient[1]).get(headers.Algorithm) if p_alg is not None: return cls._RCPT_CLASSES[p_alg].from_cose_obj(recipient, context) elif u_alg is not None: return cls._RCPT_CLASSES[u_alg].from_cose_obj(recipient, context) else: raise CoseException( "No algorithm specified in recipient structure")
def verify(cls, key: 'OKP', data: bytes, signature: bytes) -> bool: if key.crv.fullname == 'ED25519': vk = Ed25519PublicKey.from_public_bytes(key.x) elif key.crv.fullname == 'ED448': vk = Ed448PublicKey.from_public_bytes(key.x) else: raise CoseException(f"Illegal curve for OKP singing: {key.crv}") try: vk.verify(signature, data) return True except InvalidSignature: return False
def _setup_ephemeral_key(self, peer_key, optional_params: dict = None): self.key = EC2Key.generate_key(peer_key.crv, optional_params) if self.get_attr(headers.EphemeralKey) is not None: # public key was already set in the header bucket but we just generated a new ephemeral key. raise CoseException( 'Unrelated ephemeral public key found in COSE message header') else: # strip private bytes from key ephemeral_public_key = dict(self.key) del ephemeral_public_key[EC2KpD] # add to unprotected header self.uhdr_update({headers.EphemeralKey: ephemeral_public_key})
def encrypt(self, target_alg) -> bytes: # static receiver key _ = target_alg peer_key: 'EC2Key' = self.local_attrs.get(headers.StaticKey) if peer_key is None: raise CoseException( "Static receiver key cannot be None. Should be configured in 'local_attrs' of the msg." ) alg = self.get_attr(headers.Algorithm) if alg is None: raise CoseException( "The algorithm parameter should at least be included in the unprotected header" ) # if ephemeral and not set, generate ephemeral key pair if self.key is None: if alg in {EcdhEsA128KW, EcdhEsA192KW, EcdhEsA256KW}: self._setup_ephemeral_key(peer_key) else: # alg uses a static sender raise CoseException("Static sender key cannot be None.") key_bytes = self._compute_kek( (self.get_attr(headers.Algorithm)).get_key_wrap_func(), peer_key, self.key, alg) wrap_func = alg.get_key_wrap_func() return wrap_func.key_wrap( SymmetricKey(k=key_bytes, optional_params={ KpAlg: alg, KpKeyOps: [DeriveKeyOp] }), self.payload)
def decrypt(self, *args, **kwargs) -> bytes: """ Decrypts the payload. :raises CoseException: When the key is not of type 'SymmetricKey'. :returns: plaintext as bytes """ alg = self.get_attr(headers.Algorithm) nonce = self._get_nonce() if self.key is None: raise CoseException("Key cannot be None") self.key.verify(SymmetricKey, alg, [DecryptOp]) return alg.decrypt(key=self.key, ciphertext=self.payload, external_aad=self._enc_structure, nonce=nonce)
def encrypt(self, *args, **kwargs) -> bytes: """ Encrypts the payload. :raises CoseException: When the key is not of type 'SymmetricKey'. :returns: ciphertext as bytes """ # first check if key is set (since a part of the nonce can be stored in the key) if self.key is None: raise CoseException("Key cannot be None") alg = self.get_attr(headers.Algorithm) nonce = self._get_nonce() self.key.verify(SymmetricKey, alg, [EncryptOp]) return alg.encrypt(key=self.key, data=self.payload, external_aad=self._enc_structure, nonce=nonce)
def decrypt(self, target_alg: '_EncAlg') -> bytes: alg = self.get_attr(headers.Algorithm) key_ops = [DecryptOp, UnwrapOp] if alg in {A128KW, A192KW, A256KW}: kek = SymmetricKey(k=self._compute_kek(target_alg, 'decrypt'), optional_params={ KpAlg: alg, KpKeyOps: key_ops }) kek.verify(SymmetricKey, alg, [UnwrapOp, DecryptOp]) elif alg in {RsaesOaepSha512, RsaesOaepSha256, RsaesOaepSha1}: kek = self.key kek.verify(RSAKey, alg, [UnwrapOp, DecryptOp]) else: raise CoseException( f"Unsupported algorithm for key unwrapping: {alg}") return alg.key_unwrap(kek, self.payload)
def verify(self, key_type: Type['CK'], algorithm: Type['CoseAlg'], key_ops: List[Type['KEYOPS']]): """ Verify attributes of the COSE_key object.""" if not isinstance(self, key_type): raise CoseException("Wrong key type") if self.alg is not None and self.alg.identifier != algorithm.identifier: raise CoseIllegalAlgorithm( "Conflicting algorithms in key and COSE headers") if len(self.key_ops): match_key_ops = False for k in key_ops: if k in self.key_ops: match_key_ops = True if not match_key_ops: raise CoseIllegalKeyOps( f"Illegal key operations specified. Allowed: {key_ops}, found: {self.key_ops}" )
def from_id(cls, attribute: Any, allow_unknown_attributes: bool = False) -> Any: if isinstance(attribute, int) and attribute in cls.get_registered_classes(): return cls.get_registered_classes()[attribute] elif isinstance(attribute, str) and attribute in cls.get_registered_classes(): return cls.get_registered_classes()[attribute.upper()] elif isinstance(attribute, list): translated_list = [cls.from_id(attr) for attr in attribute] return translated_list elif hasattr( attribute, 'identifier' ) and attribute.identifier in cls.get_registered_classes(): return cls.get_registered_classes()[attribute.identifier] else: if allow_unknown_attributes: return attribute else: raise CoseException( f"Unknown COSE header or key attribute with value: [{cls.__name__} - {attribute}]" )
def compute_cek(self, target_alg: '_EncAlg', ops: str) -> 'SK': alg = self.get_attr(headers.Algorithm) if alg in {EcdhSsHKDF256, EcdhSsHKDF512, EcdhEsHKDF256, EcdhEsHKDF512}: if ops == "encrypt": peer_key = self.local_attrs.get(headers.StaticKey) else: if alg in {EcdhSsHKDF256, EcdhSsHKDF512}: peer_key = self.get_attr(headers.StaticKey) else: peer_key = self.get_attr(headers.EphemeralKey) else: raise CoseIllegalAlgorithm( f"Algorithm {alg} unsupported for {self.__name__}") if peer_key is None: raise CoseException("Unknown static receiver public key") peer_key.verify(EC2Key, alg, [DeriveKeyOp, DeriveBitsOp]) self.key.verify(EC2Key, alg, [DeriveKeyOp, DeriveBitsOp]) return SymmetricKey(k=self._compute_kek(target_alg, peer_key, self.key, alg), optional_params={KpAlg: target_alg})