예제 #1
0
def encryptKeyAES(options, manifestInput, encryptionInfo, payload):
    # Store the PSK ID
    pskId = manifestGet(manifestInput, 'resource.resource.manifest.payload.encryptionInfo.id.key', 'encryptionKeyId')
    if not pskId:
        LOG.critical('The aes-psk encryption mode requires a "encryptionKeyId" entry in the input JSON file')
        sys.exit(1)
    encryptionInfo["id"] = { "key" : codecs.decode(pskId, 'hex')}
    secret = options.payload_secret

    # Read the secret
    secret_data = utils.read_file(secret) if os.path.isfile(secret) else base64.b64decode(secret)
    if not secret_data:
        LOG.critical('The aes-psk encryption mode requires either a base64-encoded secret or a valid file to be passed via the "-s" option.')
        sys.exit(1)

    # generate an IV
    IV = os.urandom(nonceSize)
    # generate a new key
    key = os.urandom(nonceSize)

    cipherpayload = encryptClearText(payload, key, IV)

    # NOTE: In general, ECB is a poor choice, since it fails quickly to known-plaintext attacks. In this case, however,
    # ECB should be appropriate, since the plaintext is precisely one AES block, and it is comprised of high entropy
    # random data; therefore, the plaintext is never known.

    # Create the cipher object
    cipher = cryptoCiphers.Cipher(cryptoCiphers.algorithms.AES(bytes(secret_data)),
                                  cryptoCiphers.modes.ECB(), backend=backend) # nosec ignore bandit B404
    keyEncryptor = cipher.encryptor()
    # Encrypt the plain text
    cipherKey = keyEncryptor.update(bytes(key)) + encryptor.finalize()
    return ciphertext

    return IV, cipherKey, cipherpayload
예제 #2
0
def get_manifest_dependencies(options, manifestInput):
    dependencies = []
    for link in manifestGet(manifestInput, 'resource.resource.manifest.dependencies') or []:
        if not any(k in link for k in ('uri', 'file')):
            LOG.critical('Manifest link requires either a "uri" or a "file"'
                        'key - or both. Could only find %r' % link.keys())
            sys.exit(1)
        LOG.debug('Adding manifest link reference (URI: {}, File: {})'.format(link.get('uri', None), link.get('file', None)))

        # If file isn't provided, we attempt to download the file from provided URI
        # If the user provides the link as a filepath, just read the file
        if 'file' in link:
            linkPath = link['file']
            if not os.path.isabs(linkPath):
                linkPath = os.path.join(os.path.dirname(options.input_file.name), linkPath)
            content = utils.read_file(linkPath)
        else:
            content = utils.download_file(link['uri'])

        # Calculate the hash and length.
        manifest_hash = utils.sha_hash(content)
        manifest_length = len(content)
        LOG.debug('Linked manifest of {} bytes loaded. Hash: {}'.format(manifest_length, manifest_hash))

        link_uri = link['uri'] if 'uri' in link else link['file']
        dependencies.append({
            'hash': manifest_hash,
            'uri': link_uri,
            'size': manifest_length
        })
    return dependencies
예제 #3
0
def get_manifest_aliases(options, manifestInput):
    aliases = []
    for alias in manifestGet(manifestInput,'resource.resource.manifest.aliases') or []:
        # Ensure all required options are defined
        if not all(k in alias for k in ('file', 'uri')):
            LOG.critical('Could not create aliases, as all required keys for alias are not defined ("uri" and "file")')
            sys.exit(1)

        # Read alias file to calculate hash
        fPath = alias['file']
        if not os.path.isabs(fPath):
            fPath = os.path.join(os.path.dirname(options.input_file.name), fPath)
        content = utils.read_file(fPath)
        sha_hash = utils.sha_hash(content)
        LOG.debug('Creating ResourceAlias for file {} with SHA reference {}'.format(fPath, sha_hash))

        aliases.append(ResourceAlias(
            hash = sha_hash,
            uri = alias['uri']
        ))
    return aliases
예제 #4
0
def get_signature(options, manifestInput, enc_data):
    signatures = manifestGet(manifestInput,'signature.signatures') or []
    input_hash = manifestGet(manifestInput,'signature.hash') or b''

    # There should always be a signing key on create.
    if not hasattr(options,'private_key') or not options.private_key:
        if 'private-key' in manifestInput:
            try:
                options.private_key = open(manifestInput['private-key'],'r')
            except:
                LOG.critical('No private key specified and default key ({}) cannot be opened'.format(manifestInput['private-key']))
                sys.exit(1)
        else:
            LOG.critical('Resource is not signed and no signing key is provided.')
            sys.exit(1)

    # Get SHA-256 hash of content and sign it using private key
    sha_content = utils.sha_hash(enc_data)
    if len(signatures):
        # If a signature is provided in the input json, then the encoded content must match the provided hash
        # Signature validation is not performed, since this would require certificate acquisition, which may not be
        # possible
        if sha_content != binascii.a2b_hex(input_hash):
            LOG.critical('Manifest hash provided in input file does not match hashed output')
            LOG.critical('Expected: {0}'.format(input_hash))
            LOG.critical('Actual:   {0}'.format(binascii.b2a_hex(sha_content)))
            sys.exit(1)
        # TODO: perform best-effort signature validation

    if hasattr(options, 'private_key') and options.private_key:
        sk = ecdsa.SigningKey.from_pem(options.private_key.read())
        sig = sk.sign_digest(sha_content, sigencode=ecdsa.util.sigencode_der)

        certificates = []

        # pick a signature block with no signature in it.
        inputCerts = manifestGet(manifestInput, 'certificates') or []

        # If no certificate was provided in the manifest input or in options,
        if len(inputCerts) == 0:
            # then load the default certificate
            inputCerts = manifestInput.get('default-certificates', [])

        # If there is still no certificate,
        if len(inputCerts) == 0:
            # Search through all signature blocks for one that contains certificates but no signature
            for idx, sb in enumerate(signatures):
                 if not 'signature' in sb:
                     inputCerts = sb.get('certificates', [])
                     # This signature will be appended later so we must trim it.
                     del signatures[idx]
                     break

        for idx, cert in enumerate(inputCerts):
            if not any(k in cert for k in ('file', 'uri')):
                LOG.critical('Could not find "file" or "uri" property for certificate')
                sys.exit(1)

            # If 'file', we just use the content in local file
            if 'file' in cert:
                fPath = cert['file']
                if not os.path.isabs(fPath):
                    fPath = os.path.join(os.path.dirname(options.input_file.name), cert['file'])
                content = utils.read_file(fPath)

            # Else we download the file contents
            else:
                content = utils.download_file(cert['uri'])
            # Figure our which extension the certificate has
            contentPath = cert['file'] if 'file' in cert else cert['uri']
            ext = contentPath.rsplit('.', 1)[1]

            # Read the certificate file, and get DER encoded data
            if ext == 'pem':
                lines = content.replace(" ",'').split()
                content = binascii.a2b_base64(''.join(lines[1:-1]))

            # Verify the certificate hash algorithm
            # Extract subjectPublicKeyInfo field from X.509 certificate (see RFC3280)
            # fingerprint = utils.sha_hash(content)
            cPath = cert['file'] if 'file' in cert else cert['uri']
            certObj = None
            try:
                certObj = x509.load_der_x509_certificate(content, cryptoBackends.default_backend())
            except ValueError as e:
                LOG.critical("X.509 Certificate Error in ({file}): {error}".format(error=e, file=cPath))
                sys.exit(1)

            if not certObj:
                LOG.critical("({file}) is not a valid certificate".format(file=cPath))
                sys.exit(1)
            if not isinstance(certObj.signature_hash_algorithm, cryptoHashes.SHA256):
                LOG.critical("In ({file}): Only SHA256 certificates are supported by the mbed Cloud Update client at this time.".format(file=cPath))
                sys.exit(1)
            fingerprint = certObj.fingerprint(cryptoHashes.SHA256())

            LOG.debug('Creating certificate reference ({}) from {} with fingerprint {}'.format(idx, contentPath, fingerprint))
            uri = ''
            if 'uri' in cert:
                uri = cert['uri']
            certificates.append(CertificateReference(
                fingerprint = fingerprint,
                uri = uri
            ))

        LOG.debug('Signed hash ({}) of encoded content ({} bytes) with resulting signature {}'.format(
            sha_content, len(enc_data), sig))
        signatures.append(SignatureBlock(signature = sig, certificates = certificates))
    return ResourceSignature(
            hash = sha_content,
            signatures = signatures
        )
예제 #5
0
def get_payload_description(options, manifestInput):
    crypto_mode = manifestGet(manifestInput, 'resource.resource.manifest.encryptionMode.enum', 'encryptionMode')
    crypto_mode = crypto_mode if crypto_mode in cryptoMode.MODES else cryptoMode.name2enum(crypto_mode)
    if not crypto_mode:
        cryptname = ''
        if hasattr(options, 'encrypt_payload') and options.encrypt_payload:
            cryptname = 'aes-128-ctr-ecc-secp256r1-sha256'
        else:
            cryptname = 'none-ecc-secp256r1-sha256'
        crypto_mode = cryptoMode.name2enum(cryptname)
    crypto_name = cryptoMode.MODES.get(crypto_mode).name

    payload_file = manifestGet(manifestInput, 'resource.resource.manifest.payload.reference.file', 'payloadFile')
    if hasattr(options, 'payload') and options.payload:
        payload_file = options.payload.name
    # payload URI defaults to the payload file path if no payload URI is supplied.
    payload_uri = payload_file
    payload_uri = manifestGet(manifestInput, 'resource.resource.manifest.payload.reference.uri', 'payloadUri')
    if hasattr(options, 'uri') and options.uri:
        payload_uri = options.uri

    dependencies = manifestGet(manifestInput, 'resource.resource.manifest.dependencies')
    if not any((payload_uri, payload_file)) and not dependencies:
        LOG.critical('No payload was specified and no dependencies were provided.')
        sys.exit(1)
        # fwFile/fwUri is optional, so if not provided we just return empty
        return None

    payload_hash = manifestGet(manifestInput, 'resource.resource.manifest.payload.reference.hash', 'payloadHash')
    payload_size = manifestGet(manifestInput, 'resource.resource.manifest.payload.reference.size', 'payloadSize')
    if payload_hash:
        LOG.debug('Found hash in input, skipping payload load. Hash: {}'.format(payload_hash))
        payload_hash = binascii.a2b_hex(payload_hash)
    else:
        if payload_file:
            payload_filePath = payload_file
            # If file path is not absolute, then make it relative to input file
            if os.path.isabs(payload_file) or not options.input_file.isatty():
                payload_filePath = os.path.join(os.path.dirname(options.input_file.name), payload_file)
            content = utils.read_file(payload_filePath)
        else:
            content = utils.download_file(payload_uri)

        # Read payload input, record length and hash it
        payload_size = len(content)
        payload_hash = utils.sha_hash(content)
        LOG.debug('payload of {} bytes loaded. Hash: {}'.format(payload_size, payload_hash))

    # Ensure the cryptoMode is valid
    if not crypto_mode in cryptoMode.MODES:
        valid_modes = ", ".join((('%s (%d)' % (v.name, k) for k, v in cryptoMode.MODES.items())))
        LOG.critical('Could not find specified cryptoMode (%d) in list of valid encryption modes. '
            'Please use on of the following: %r' % (crypto_mode, valid_modes))
        sys.exit(1)

    # Get encryption options for the provided mode
    should_encrypt = ARM_UC_mmCryptoModeShouldEncrypt(crypto_mode)
    if hasattr(options, 'encrypt_payload') and options.encrypt_payload and not should_encrypt:
        LOG.critical('--encrypt-payload specified, but cryptoMode({cryptoMode}) does not support encryption.'.format(**manifestInput))
        sys.exit(1)

    encryptionInfo = None
    if should_encrypt:
        LOG.debug('Crypto mode {} ({}) requires encryption. Will ensure ciphers are valid and loaded...'\
                .format(crypto_mode, crypto_name))
        if not options.encrypt_payload:
            LOG.critical('Specified crypto mode ({cryptoMode}) requires encryption, '
                        'but --encrypt-payload not specified.'.format(**manifestInput))
            sys.exit(1)
        cipherFile = manifestGet(manifestInput, 'resource.resource.manifest.payload.encryptionInfo.file', 'encryptedPayloadFile')
        if not cipherFile:
            LOG.critical('"resource.resource.manifest.payload.encryptionInfo.file" must be specified in the JSON input'
                        'file when --encrypt-payload is specified on the command-line.')
            sys.exit(1)
        encryptionInfo = {}

        cipherModes = {
                'aes-psk' : encryptKeyAES
        }

        if not options.encrypt_payload in cipherModes:
            LOG.critical('Specified encryption mode "{mode}" is not supported'.format(mode=options.encrypt_payload))
            sys.exit(1)

        init_vector, cipherKey, cipherpayload = cipherModes.get(options.encrypt_payload)(options, manifestInput, encryptionInfo, content)

        with open(cipherFile,'wb') as f:
            f.write(cipherpayload)

        encryptionInfo["key"] = { "cipherKey": cipherKey }
        encryptionInfo["initVector"] = init_vector
        LOG.debug('payload ({} bytes) encrypted. Cipher key: {}, cipher payload ouptut to : {}'.format(len(content), cipherKey, cipherFile))

    else:
        LOG.debug('Will not encrypt payload as crypto mode {} ({}) does not require it'.format(crypto_mode, crypto_name))

    return PayloadDescription(
        **{
            "storageIdentifier": manifestGet(manifestInput,'resource.resource.manifest.payload.storageIdentifier', 'storageIdentifier') or "default",
            "reference": ResourceReference(
                hash = payload_hash,
                uri = payload_uri,
                size = payload_size
            ),
            "encryptionInfo": encryptionInfo
        }
    )
예제 #6
0
def get_signature(options, manifestInput, enc_data):
    signatures = manifestGet(manifestInput, 'signature.signatures') or []
    input_hash = manifestGet(manifestInput, 'signature.hash') or b''
    signing_tool = manifestGet(manifestInput, 'signing-tool') or ''
    if getattr(options, 'signing_tool', None):
        signing_tool = options.signing_tool

    # There should always be a signing key or signing tool on create.
    if not signing_tool and not getattr(options, 'private_key', None):
        if 'private-key' in manifestInput:
            try:
                options.private_key = open(manifestInput['private-key'], 'r')
            except:
                LOG.critical(
                    'No private key specified and default key ({}) cannot be opened'
                    .format(manifestInput['private-key']))
                sys.exit(1)
        else:
            LOG.critical(
                'Resource is not signed and no signing key is provided.')
            sys.exit(1)

    # Get SHA-256 hash of content and sign it using private key
    sha_content = utils.sha_hash(enc_data)
    if len(signatures):
        # If a signature is provided in the input json, then the encoded content must match the provided hash
        # Signature validation is not performed, since this would require certificate acquisition, which may not be
        # possible
        if sha_content != binascii.a2b_hex(input_hash):
            LOG.critical(
                'Manifest hash provided in input file does not match hashed output'
            )
            LOG.critical('Expected: {0}'.format(input_hash))
            LOG.critical('Actual:   {0}'.format(binascii.b2a_hex(sha_content)))
            sys.exit(1)
        # TODO: perform best-effort signature validation

    signature = None
    if signing_tool:
        # get the key id
        key_id = manifestGet(manifestInput, 'signing-key-id')
        if hasattr(options, 'signing_key_id') and options.signing_key_id:
            key_id = options.signing_key_id
        digest_algo = 'sha256'
        infile = None
        with tempfile.NamedTemporaryFile(delete=False) as f:
            infile = f.name
            f.write(enc_data)
            f.flush()
            LOG.debug('Temporary manifest file: {}'.format(infile))
        outfile = None
        with tempfile.NamedTemporaryFile(delete=False) as f:
            outfile = f.name
            LOG.debug('Temporary signature file: {}'.format(outfile))

        try:
            cmd = [signing_tool, digest_algo, key_id, infile, outfile]
            LOG.debug('Running "{}" to sign manifest.'.format(' '.join(cmd)))
            # This command line is constructed internally, so we ignore bandit
            # warnings about executing a Popen. See:
            # https://bandit.readthedocs.io/en/latest/plugins/b603_subprocess_without_shell_equals_true.html
            p = subprocess.Popen(cmd)  #nosec
            p.wait()
            if p.returncode != 0:
                LOG.critical('Signing tool failed.')
                sys.exit(1)
            with open(outfile, 'rb') as f:
                signature = f.read()

        except:
            LOG.critical('Failed to execute signing tool.')
            sys.exit(1)
        finally:
            os.unlink(infile)
            os.unlink(outfile)
        LOG.debug('Signature: {}'.format(
            binascii.b2a_hex(signature).decode('utf-8')))

    elif hasattr(options, 'private_key') and options.private_key:
        sk = ecdsa.SigningKey.from_pem(options.private_key.read())
        signature = sk.sign_digest(sha_content,
                                   sigencode=ecdsa.util.sigencode_der)

    certificates = []

    # pick a signature block with no signature in it.
    inputCerts = manifestGet(manifestInput, 'certificates') or []

    # If no certificate was provided in the manifest input or in options,
    if len(inputCerts) == 0:
        # then load the default certificate
        inputCerts = manifestInput.get('default-certificates', [])

    # If there is still no certificate,
    if len(inputCerts) == 0:
        # Search through all signature blocks for one that contains certificates but no signature
        for idx, sb in enumerate(signatures):
            if not 'signature' in sb:
                inputCerts = sb.get('certificates', [])
                # This signature will be appended later so we must trim it.
                del signatures[idx]
                break

    for idx, cert in enumerate(inputCerts):
        if not any(k in cert for k in ('file', 'uri')):
            LOG.critical(
                'Could not find "file" or "uri" property for certificate')
            sys.exit(1)

        # If 'file', we just use the content in local file
        if 'file' in cert:
            fPath = cert['file']
            if not os.path.isabs(fPath):
                fPath = os.path.join(os.path.dirname(options.input_file.name),
                                     cert['file'])
            content = utils.read_file(fPath)

        # Else we download the file contents
        else:
            content = utils.download_file(cert['uri'])
        # Figure our which extension the certificate has
        contentPath = cert['file'] if 'file' in cert else cert['uri']
        ext = contentPath.rsplit('.', 1)[1]

        # Read the certificate file, and get DER encoded data
        if ext == 'pem':
            lines = content.replace(" ", '').split()
            content = binascii.a2b_base64(''.join(lines[1:-1]))

        # Verify the certificate hash algorithm
        # Extract subjectPublicKeyInfo field from X.509 certificate (see RFC3280)
        # fingerprint = utils.sha_hash(content)
        cPath = cert['file'] if 'file' in cert else cert['uri']
        certObj = None
        try:
            certObj = x509.load_der_x509_certificate(
                content, cryptoBackends.default_backend())
        except ValueError as e:
            LOG.critical("X.509 Certificate Error in ({file}): {error}".format(
                error=e, file=cPath))
            sys.exit(1)

        if not certObj:
            LOG.critical(
                "({file}) is not a valid certificate".format(file=cPath))
            sys.exit(1)
        if not isinstance(certObj.signature_hash_algorithm,
                          cryptoHashes.SHA256):
            LOG.critical(
                "In ({file}): Only SHA256 certificates are supported by the Device Management Update client at this time."
                .format(file=cPath))
            sys.exit(1)
        fingerprint = certObj.fingerprint(cryptoHashes.SHA256())

        LOG.debug(
            'Creating certificate reference ({}) from {} with fingerprint {}'.
            format(idx, contentPath, fingerprint))
        uri = ''
        if 'uri' in cert:
            uri = cert['uri']
        certificates.append(
            CertificateReference(fingerprint=fingerprint, uri=uri))

        LOG.debug(
            'Signed hash ({}) of encoded content ({} bytes) with resulting signature {}'
            .format(sha_content, len(enc_data), signature))
    if signature:
        signatures.append(
            SignatureBlock(signature=signature, certificates=certificates))
    return ResourceSignature(hash=sha_content, signatures=signatures)