Ejemplo n.º 1
0
def get_mar_verification_key(cert_type, fmt, keyid):
    """Get the public key file for the format/cert_type.

    Args:
        cert_type (str): the cert scope string
        fmt (str): the signing format
        keyid (str): the key id to use (can be None)

    Raises:
        SigningScriptError: if no key is found

    Returns:
        str: the public key to use with ``-k``

    """
    # Cert types are like ...
    cert_type = cert_type.split(":")[-1]
    data_dir = os.path.join(os.path.dirname(__file__), "data")
    try:
        if keyid is None:
            return os.path.join(data_dir, _DEFAULT_MAR_VERIFY_KEYS[fmt][cert_type])
        else:
            # Make sure you can't try and read outside of the data directory
            if "/" in keyid:
                raise SigningScriptError("/ not allowed in keyids")
            keyid = os.path.basename(keyid)
            return os.path.join(data_dir, f"{keyid}.pem")
    except KeyError as err:
        raise SigningScriptError(f"Can't find mar verify key for {fmt}, {cert_type} ({keyid}):\n{err}")
Ejemplo n.º 2
0
async def sign_file_with_autograph(context, from_, fmt, to=None):
    """Signs a file with autograph and writes the result to arg `to` or `from_` if `to` is None.

    Args:
        context (Context): the signing context
        from_ (str): the source file to sign
        fmt (str): the format to sign with
        to (str, optional): the target path to sign to. If None, overwrite
            `from_`. Defaults to None.

    Raises:
        Requests.RequestException: on failure

    Returns:
        str: the path to the signed file

    """
    if not utils.is_autograph_signing_format(fmt):
        raise SigningScriptError(
            "No signing servers found supporting signing format {}".format(
                fmt))
    cert_type = task.task_cert_type(context)
    servers = get_suitable_signing_servers(context.signing_servers, cert_type,
                                           [fmt])
    if not servers:
        raise SigningScriptError(
            "No signing servers found with cert type {}".format(cert_type))
    s = servers[0]
    to = to or from_

    with open(from_, 'rb') as fin:
        input_bytes = fin.read()

    # build and run the signature request
    sign_req = [{"input": base64.b64encode(input_bytes)}]
    log.debug("using the default autograph keyid for %s", s.user)

    url = "%s/sign/file" % s.server

    async def make_sign_req():
        auth = HawkAuth(id=s.user, key=s.password)
        with requests.Session() as session:
            r = session.post(url, json=sign_req, auth=auth)
            log.debug("Autograph response: {}".format(
                r.text[:120] if len(r.text) >= 120 else r.text))
            r.raise_for_status()
            return r.json()

    sign_resp = await retry_async(make_sign_req)

    with open(to, 'wb') as fout:
        fout.write(base64.b64decode(sign_resp[0]['signed_file']))

    log.info("autograph wrote signed_file %s to %s", from_, to)
    return to
Ejemplo n.º 3
0
async def sign_gpg_with_autograph(context, from_, fmt):
    """Signs file with autograph and writes the results to a file.

    Args:
        context (Context): the signing context
        from_ (str): the source file to sign
        fmt (str): the format to sign with

    Raises:
        Requests.RequestException: on failure
        SigningScriptError: when no suitable signing server is found for fmt

    Returns:
        list: the path to the signed file, and sig.

    """
    if not utils.is_autograph_signing_format(fmt):
        raise SigningScriptError(f"Not an autograph format: {fmt}")
    cert_type = task.task_cert_type(context)
    servers = get_suitable_signing_servers(context.signing_servers, cert_type,
                                           [fmt], raise_on_empty_list=True)
    s = servers[0]
    to = f"{from_}.asc"
    input_bytes = open(from_, 'rb').read()
    signature = await sign_with_autograph(s, input_bytes, fmt, 'data')
    with open(to, 'w') as fout:
        fout.write(signature)
    return [from_, to]
Ejemplo n.º 4
0
def _get_tarfile_compression(compression):
    compression = compression.lstrip('.')
    if compression not in ('bz2', 'gz'):
        raise SigningScriptError(
            "{} not a supported tarfile compression format!".format(compression)
        )
    return compression
Ejemplo n.º 5
0
async def sign_mar384_with_autograph_hash(context, from_, fmt, to=None):
    """Signs a hash with autograph, injects it into the file, and writes the result to arg `to` or `from_` if `to` is None.

    Args:
        context (Context): the signing context
        from_ (str): the source file to sign
        fmt (str): the format to sign with
        to (str, optional): the target path to sign to. If None, overwrite
            `from_`. Defaults to None.

    Raises:
        Requests.RequestException: on failure
        SigningScriptError: when no suitable signing server is found for fmt

    Returns:
        str: the path to the signed file

    """
    cert_type = task.task_cert_type(context)
    # Get any key id that the task may have specified
    fmt, keyid = utils.split_autograph_format(fmt)
    # Call to check that we have a server available
    get_suitable_signing_servers(context.signing_servers, cert_type, [fmt],
                                 raise_on_empty_list=True)

    hash_algo, expected_signature_length = 'sha384', 512

    # Add a dummy signature into a temporary file (TODO: dedup with mardor.cli do_hash)
    with tempfile.TemporaryFile() as tmp:
        with open(from_, 'rb') as f:
            add_signature_block(f, tmp, hash_algo)

        tmp.seek(0)

        with MarReader(tmp) as m:
            hashes = m.calculate_hashes()
        h = hashes[0][1]

    signature = await sign_hash_with_autograph(context, h, fmt, keyid)

    # Add a signature to the MAR file (TODO: dedup with mardor.cli do_add_signature)
    if len(signature) != expected_signature_length:
        raise SigningScriptError(
            "signed mar hash signature has invalid length for hash algo {}. Got {} expected {}.".format(hash_algo, len(signature), expected_signature_length)
        )

    # use the tmp file in case param `to` is `from_` which causes stream errors
    tmp_dst = tempfile.NamedTemporaryFile(mode='w+b', delete=False)
    with open(tmp_dst.name, 'w+b') as dst:
        with open(from_, 'rb') as src:
            add_signature_block(src, dst, hash_algo, signature)

    to = to or from_
    shutil.copyfile(tmp_dst.name, to)
    os.unlink(tmp_dst.name)

    verify_mar_signature(cert_type, fmt, to, keyid)

    log.info("wrote mar with autograph signed hash %s to %s", from_, to)
    return to
Ejemplo n.º 6
0
async def sign_hash_with_autograph(context, hash_, fmt, keyid=None):
    """Signs hash with autograph and returns the result.

    Args:
        context (Context): the signing context
        hash_ (bytes): the input hash to sign
        fmt (str): the format to sign with
        keyid (str): which key to use on autograph (optional)

    Raises:
        Requests.RequestException: on failure
        SigningScriptError: when no suitable signing server is found for fmt

    Returns:
        bytes: the signature

    """
    if not utils.is_autograph_signing_format(fmt):
        raise SigningScriptError(f"Not an autograph format: {fmt}")
    cert_type = task.task_cert_type(context)
    servers = get_suitable_signing_servers(context.signing_servers, cert_type,
                                           [fmt], raise_on_empty_list=True)
    s = servers[0]
    signature = base64.b64decode(await sign_with_autograph(s, hash_, fmt, 'hash',  keyid))
    return signature
Ejemplo n.º 7
0
def get_autograph_config(autograph_configs, cert_type, signing_formats, raise_on_empty=False):
    """Get the autograph config for given `signing_formats` and `cert_type`.

    Args:
        autograph_configs (dict of lists of lists): the contents of
            `autograph_configs`.
        cert_type (str): the certificate type - essentially signing level,
            separating release vs nightly vs dep.
        signing_formats (list): the signing formats the server needs to support
        raise_on_empty (bool): flag to raise errors. Optional. Defaults to False.

    Raises:
        SigningScriptError: when no suitable signing server is found

    Returns:
        An Autograph object

    """
    for a in autograph_configs.get(cert_type, []):
        if a and (set(a.formats) & set(signing_formats)):
            return a

    if raise_on_empty:
        raise SigningScriptError(f"No autograph config found with cert type {cert_type} and formats {signing_formats}")
    return None
Ejemplo n.º 8
0
async def sign_xpi(context, orig_path, fmt):
    """Sign language packs with autograph.

    This validates both the file extension and the language pack ID is sane.

    Args:
        context (Context): the signing context
        orig_path (str): the source file to sign
        fmt (str): the format to sign with

    Returns:
        str: the path to the signed xpi

    """
    file_base, file_extension = os.path.splitext(orig_path)

    if file_extension not in (".xpi", ".zip"):
        raise SigningScriptError("Expected a .xpi")

    ext_id = _extension_id(orig_path, fmt)
    log.info("Identified {} as extension id: {}".format(orig_path, id))
    kwargs = {"extension_id": ext_id}
    # Sign the appropriate inner files
    await sign_file_with_autograph(context, orig_path, fmt, **kwargs)
    return orig_path
Ejemplo n.º 9
0
async def sign_omnija(context, orig_path, fmt):
    """Call the appropriate helper function to do omnija signing.

    Args:
        context (Context): the signing context
        orig_path (str): the source file to sign
        fmt (str): the format to sign with

    Raises:
        SigningScriptError: on unknown suffix.

    Returns:
        str: the path to the signed archive

    """
    file_base, file_extension = os.path.splitext(orig_path)
    # Convert dmg to tarball
    if file_extension == ".dmg":
        await _convert_dmg_to_tar_gz(context, orig_path)
        orig_path = "{}.tar.gz".format(file_base)
    ext_to_fn = {".zip": sign_omnija_zip, ".tar.bz2": sign_omnija_tar, ".tar.gz": sign_omnija_tar}
    for ext, signing_func in ext_to_fn.items():
        if orig_path.endswith(ext):
            return await signing_func(context, orig_path, fmt)
    raise SigningScriptError("Unknown omnija file format for {}".format(orig_path))
Ejemplo n.º 10
0
def get_suitable_signing_servers(signing_servers, cert_type, signing_formats, raise_on_empty_list=False):
    """Get the list of signing servers for given `signing_formats` and `cert_type`.

    Args:
        signing_servers (dict of lists of lists): the contents of
            `signing_server_config`.
        cert_type (str): the certificate type - essentially signing level,
            separating release vs nightly vs dep.
        signing_formats (list): the signing formats the server needs to support
        raise_on_empty_list (bool): flag to raise errors. Optional. Defaults to False.

    Raises:
        FailedSubprocess: on subprocess error while signing.
        SigningScriptError: when no suitable signing server is found

    Returns:
        list of lists: the list of signing servers.

    """
    if cert_type not in signing_servers:
        suitable_signing_servers = []
    else:
        suitable_signing_servers = [s for s in signing_servers[cert_type] if set(signing_formats) & set(s.formats)]

    if raise_on_empty_list and not suitable_signing_servers:
        raise SigningScriptError(
            f"No signing servers found with cert type {cert_type} and formats {signing_formats}"
        )
    else:
        return suitable_signing_servers
Ejemplo n.º 11
0
async def sign_file_with_autograph(context, from_, fmt, to=None):
    """Signs file with autograph and writes the results to a file.

    Args:
        context (Context): the signing context
        from_ (str): the source file to sign
        fmt (str): the format to sign with
        to (str, optional): the target path to sign to. If None, overwrite
                            `from_`. Defaults to None.

    Raises:
        Requests.RequestException: on failure
        SigningScriptError: when no suitable signing server is found for fmt

    Returns:
        str: the path to the signed file

    """
    if not utils.is_autograph_signing_format(fmt):
        raise SigningScriptError(f"Not an autograph format: {fmt}")
    cert_type = task.task_cert_type(context)
    servers = get_suitable_signing_servers(context.signing_servers, cert_type,
                                           [fmt], raise_on_empty_list=True)
    s = servers[0]
    to = to or from_
    input_bytes = open(from_, 'rb').read()
    signed_bytes = base64.b64decode(await sign_with_autograph(s, input_bytes, fmt, 'file'))
    with open(to, 'wb') as fout:
        fout.write(signed_bytes)
    return to
Ejemplo n.º 12
0
def _extension_id(filename, fmt):
    """Return a list of id's for the langpacks.

    Side effect of additionally verifying langpack manifests.
    """
    xpi = zipfile.ZipFile(filename, "r")
    manifest = {}
    for manifest_name in ("manifest.json", "webextension/manifest.json"):
        try:
            with xpi.open(manifest_name, "r") as f:
                manifest = json.load(f)
                break
        except KeyError:
            log.debug("{} doesn't exist in {}...".format(manifest_name, filename))
    if not manifest.get("applications", {}).get("gecko", {}).get("id"):
        raise SigningScriptError("{} is not a valid xpi".format(filename))
    if "langpack" in fmt and not (
        "languages" in manifest and "langpack_id" in manifest and LANGPACK_RE.match(manifest["applications"]["gecko"]["id"]) and filename.endswith(".xpi")
    ):
        raise SigningScriptError("{} is not a valid langpack".format(filename))
    return manifest["applications"]["gecko"]["id"]
Ejemplo n.º 13
0
async def _create_zipfile(context, to, files, tmp_dir=None, mode="w"):
    work_dir = context.config["work_dir"]
    tmp_dir = tmp_dir or os.path.join(work_dir, "unzipped")
    try:
        log.info("Creating zipfile {}...".format(to))
        with zipfile.ZipFile(to, mode=mode, compression=zipfile.ZIP_DEFLATED) as z:
            for f in files:
                relpath = os.path.relpath(f, tmp_dir)
                z.write(f, arcname=relpath)
        return to
    except Exception as e:
        raise SigningScriptError(e)
Ejemplo n.º 14
0
async def _create_tarfile(context, to, files, compression, tmp_dir=None):
    work_dir = context.config["work_dir"]
    tmp_dir = tmp_dir or os.path.join(work_dir, "untarred")
    compression = _get_tarfile_compression(compression)
    try:
        log.info("Creating tarfile {}...".format(to))
        with tarfile.open(to, mode="w:{}".format(compression)) as t:
            for f in files:
                relpath = os.path.relpath(f, tmp_dir)
                t.add(f, arcname=relpath, filter=_owner_filter)
        return to
    except Exception as e:
        raise SigningScriptError(e)
Ejemplo n.º 15
0
async def _extract_tarfile(context, from_, compression, tmp_dir=None):
    work_dir = context.config["work_dir"]
    tmp_dir = tmp_dir or os.path.join(work_dir, "untarred")
    compression = _get_tarfile_compression(compression)
    try:
        files = []
        rm(tmp_dir)
        utils.mkdir(tmp_dir)
        with tarfile.open(from_, mode="r:{}".format(compression)) as t:
            t.extractall(path=tmp_dir)
            for name in t.getnames():
                path = os.path.join(tmp_dir, name)
                os.path.isfile(path) and files.append(path)
        return files
    except Exception as e:
        raise SigningScriptError(e)
Ejemplo n.º 16
0
async def _extract_zipfile(context, from_, files=None, tmp_dir=None):
    work_dir = context.config["work_dir"]
    tmp_dir = tmp_dir or os.path.join(work_dir, "unzipped")
    log.debug("Extracting {} from {} to {}...".format(files or "all files", from_, tmp_dir))
    try:
        extracted_files = []
        rm(tmp_dir)
        utils.mkdir(tmp_dir)
        with zipfile.ZipFile(from_, mode="r") as z:
            if files is not None:
                for name in files:
                    z.extract(name, path=tmp_dir)
                    extracted_files.append(os.path.join(tmp_dir, name))
            else:
                for name in z.namelist():
                    extracted_files.append(os.path.join(tmp_dir, name))
                z.extractall(path=tmp_dir)
        return extracted_files
    except Exception as e:
        raise SigningScriptError(e)
Ejemplo n.º 17
0
async def sign_authenticode_zip(context, orig_path, fmt, *, authenticode_comment=None, **kwargs):
    """Sign a zipfile with authenticode, using autograph as a backend.

    Extract the zip and only sign unsigned files that don't match certain
    patterns (see `_should_sign_windows`). Then recreate the zip.

    Args:
        context (Context): the signing context
        orig_path (str): the source file to sign
        fmt (str): the format to sign with
        comment (str): The authenticode comment to sign with, if present.
                       currently only used for msi files.
                       (Defaults to None)

    Returns:
        str: the path to the signed zip

    """
    file_base, file_extension = os.path.splitext(orig_path)
    # This will get cleaned up when we nuke `work_dir`. Clean up at that point
    # rather than immediately after `sign_signcode`, to optimize task runtime
    # speed over disk space.
    tmp_dir = None
    # Extract the zipfile
    if file_extension == ".zip":
        tmp_dir = tempfile.mkdtemp(prefix="zip", dir=context.config["work_dir"])
        files = await _extract_zipfile(context, orig_path, tmp_dir=tmp_dir)
    else:
        files = [orig_path]
    files_to_sign = [file for file in files if _should_sign_windows(file)]
    if not files_to_sign:
        raise SigningScriptError("Did not find any files to sign, all files: {}".format(files))

    # Sign the appropriate inner files
    tasks = [sign_authenticode_file(context, file_, fmt, authenticode_comment=authenticode_comment) for file_ in files_to_sign]
    done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
    [f.result() for f in done]
    if file_extension == ".zip":
        # Recreate the zipfile
        await _create_zipfile(context, orig_path, files, tmp_dir=tmp_dir)
    return orig_path
def verify_mar_signature(cert_type, fmt, mar):
    """Verify a mar signature, via mardor.

    Args:
        cert_type (str): the cert scope string
        fmt (str): the signing format
        mar (str): the path to the mar file

    Raises:
        SigningScriptError: if the signature doesn't verify, or the nick isn't found

    """
    mar_verify_nick = get_mar_verification_nick(cert_type, fmt)
    try:
        mar_path = os.path.join(os.path.dirname(sys.executable), 'mar')
        cmd = [mar_path, '-k', mar_verify_nick, '-v', mar]
        log.info("Running %s", cmd)
        subprocess.check_call(cmd, stdout=sys.stdout, stderr=sys.stderr)
        log.info("Verified signature.")
    except subprocess.CalledProcessError as e:
        raise SigningScriptError(e)
def get_mar_verification_nick(cert_type, fmt):
    """Get the mardor nick for the format/cert_type.

    Args:
        cert_type (str): the cert scope string
        fmt (str): the signing format

    Raises:
        SigningScriptError: if no nick is found

    Returns:
        str: the mardor nick to use with ``-k``

    """
    cert_type = cert_type.split(':')[-1]
    try:
        return ':mozilla-{}'.format(_MAR_VERIFY_FORMATS[fmt][cert_type])
    except KeyError as err:
        raise SigningScriptError(
            "Can't find mar verify format for {}, {}:\n{}".format(
                fmt, cert_type, err))
Ejemplo n.º 20
0
async def sign_with_autograph(server, input_bytes, fmt, autograph_method, keyid=None,
                              extension_id=None):
    """Signs data with autograph and returns the result.

    Args:
        server (SigningServer): the server to connect to sign
        input_bytes (bytes): the source data to sign
        fmt (str): the format to sign with
        autograph_method (str): which autograph method to use to sign. must be
                                one of 'file', 'hash', or 'data'
        keyid (str): which key to use on autograph (optional)
        extension_id (str): which id to send to autograph for the extension (optional)

    Raises:
        Requests.RequestException: on failure
        SigningScriptError: when no suitable signing server is found for fmt

    Returns:
        bytes: the signed data

    """
    if autograph_method not in {'file', 'hash', 'data'}:
        raise SigningScriptError(f"Unsupported autograph method: {autograph_method}")

    sign_req = make_signing_req(input_bytes, server, fmt, keyid, extension_id)

    log.debug("signing data with format %s with %s", fmt, autograph_method)

    url = f"{server.server}/sign/{autograph_method}"

    sign_resp = await retry_async(call_autograph, args=(url, server.user,
                                                        server.password,
                                                        sign_req),
                                  attempts=3, sleeptime_kwargs={'delay_factor': 2.0})

    if autograph_method == 'file':
        return sign_resp[0]['signed_file']
    else:
        return sign_resp[0]['signature']
Ejemplo n.º 21
0
def verify_mar_signature(cert_type, fmt, mar, keyid=None):
    """Verify a mar signature, via mardor.

    Args:
        cert_type (str): the cert scope string
        fmt (str): the signing format
        mar (str): the path to the mar file
        keyid (str, optional): the key id to use (can be None)

    Raises:
        SigningScriptError: if the signature doesn't verify, or the nick isn't found

    """
    mar_verify_key = get_mar_verification_key(cert_type, fmt, keyid)
    try:
        mar_path = os.path.join(os.path.dirname(sys.executable), "mar")
        cmd = [mar_path, "-k", mar_verify_key, "-v", mar]
        log.info("Running %s", cmd)
        subprocess.check_call(cmd, stdout=sys.stdout, stderr=sys.stderr)
        log.info("Verified signature.")
    except subprocess.CalledProcessError as e:
        raise SigningScriptError(e)
Ejemplo n.º 22
0
def _langpack_id(filename):
    """Return a list of id's for the langpacks.

    Side Affect of checking if filenames are actually langpacks.
    """
    langpack = zipfile.ZipFile(filename, 'r')
    id = None
    with langpack.open('manifest.json', 'r') as f:
        manifest = json.load(f)
        if not ('languages' in manifest and
                'langpack_id' in manifest and
                'applications' in manifest and
                'gecko' in manifest['applications'] and
                'id' in manifest['applications']['gecko'] and
                LANGPACK_RE.match(
                    manifest['applications']['gecko']['id']
                )):
            raise SigningScriptError(
                '{} is not a valid langpack'.format(filename)
                )
        id = manifest['applications']['gecko']['id']
    return id
async def sign_signcode(context, orig_path, fmt):
    """Sign a zipfile with authenticode.

    Extract the zip and only sign unsigned files that don't match certain
    patterns (see `_should_sign_windows`). Then recreate the zip.

    Args:
        context (Context): the signing context
        orig_path (str): the source file to sign
        fmt (str): the format to sign with

    Returns:
        str: the path to the signed zip

    """
    file_base, file_extension = os.path.splitext(orig_path)
    # This will get cleaned up when we nuke `work_dir`. Clean up at that point
    # rather than immediately after `sign_signcode`, to optimize task runtime
    # speed over disk space.
    tmp_dir = None
    # Extract the zipfile
    if file_extension == '.zip':
        tmp_dir = tempfile.mkdtemp(prefix="zip",
                                   dir=context.config['work_dir'])
        files = await _extract_zipfile(context, orig_path, tmp_dir=tmp_dir)
    else:
        files = [orig_path]
    files_to_sign = [file for file in files if _should_sign_windows(file)]
    if not files_to_sign:
        raise SigningScriptError(
            "Did not find any files to sign, all files: {}".format(files))
    # Sign the appropriate inner files
    for from_ in files_to_sign:
        await sign_file(context, from_, fmt)
    if file_extension == '.zip':
        # Recreate the zipfile
        await _create_zipfile(context, orig_path, files, tmp_dir=tmp_dir)
    return orig_path
Ejemplo n.º 24
0
async def sign_with_autograph(session, server, input_file, fmt, autograph_method, keyid=None, extension_id=None):
    """Signs data with autograph and returns the result.

    Args:
        session (aiohttp.ClientSession): client session object
        server (Autograph): the server to connect to sign
        input_file (file object): the source data to sign
        fmt (str): the format to sign with
        autograph_method (str): which autograph method to use to sign. must be
                                one of 'file', 'hash', or 'data'
        keyid (str): which key to use on autograph (optional)
        extension_id (str): which id to send to autograph for the extension (optional)

    Raises:
        aiohttp.ClientError: on failure
        SigningScriptError: when no suitable signing server is found for fmt

    Returns:
        bytes: the signed data

    """
    if autograph_method not in {"file", "hash", "data"}:
        raise SigningScriptError(f"Unsupported autograph method: {autograph_method}")

    keyid = keyid or server.key_id
    sign_req = make_signing_req(input_file, fmt, keyid, extension_id)

    url = f"{server.url}/sign/{autograph_method}"

    sign_resp = await retry_async(
        call_autograph, args=(session, url, server.client_id, server.access_key, sign_req), attempts=3, sleeptime_kwargs={"delay_factor": 2.0}
    )

    if autograph_method == "file":
        return sign_resp[0]["signed_file"]
    else:
        return sign_resp[0]["signature"]
Ejemplo n.º 25
0
def _get_tarfile_compression(compression):
    compression = compression.lstrip(".")
    if compression not in ("bz2", "gz"):
        raise SigningScriptError("{} not a supported tarfile compression format!".format(compression))
    return compression
async def sign_file_with_autograph(context, from_, fmt, to=None):
    """Signs a file with autograph and writes the result to arg `to` or `from_` if `to` is None.

    Args:
        context (Context): the signing context
        from_ (str): the source file to sign
        fmt (str): the format to sign with
        to (str, optional): the target path to sign to. If None, overwrite
            `from_`. Defaults to None.

    Raises:
        Requests.RequestException: on failure
        SigningScriptError: when no suitable signing server is found for fmt

    Returns:
        str: the path to the signed file

    """
    if not utils.is_autograph_signing_format(fmt):
        raise SigningScriptError(
            "No signing servers found supporting signing format {}".format(
                fmt))
    cert_type = task.task_cert_type(context)
    servers = get_suitable_signing_servers(context.signing_servers,
                                           cert_type, [fmt],
                                           raise_on_empty_list=True)
    s = servers[0]
    to = to or from_

    with open(from_, 'rb') as fin:
        input_bytes = fin.read()

    # We need to base64 data for autograph. b64encode() returns bytes, though. We need utf8 strings
    # to make Python's JSON decoder happy
    base64_input = base64.b64encode(input_bytes).decode('utf-8')

    # build and run the signature request
    sign_req = [{"input": base64_input}]

    if utils.is_apk_autograph_signing_format(fmt):
        # We don't want APKs to have their compression changed
        sign_req[0]['options'] = {'zip': 'passthrough'}

        if utils.is_sha1_apk_autograph_signing_format(fmt):
            # We ask for a SHA1 digest from Autograph
            # https://github.com/mozilla-services/autograph/pull/166/files
            sign_req[0]['options']['pkcs7_digest'] = "SHA1"

    log.debug("using the default autograph keyid for %s", s.user)

    url = "%s/sign/file" % s.server

    async def make_sign_req():
        auth = HawkAuth(id=s.user, key=s.password)
        with requests.Session() as session:
            r = session.post(url, json=sign_req, auth=auth)
            log.debug("Autograph response: {}".format(
                r.text[:120] if len(r.text) >= 120 else r.text))
            r.raise_for_status()
            return r.json()

    sign_resp = await retry_async(make_sign_req)

    with open(to, 'wb') as fout:
        fout.write(base64.b64decode(sign_resp[0]['signed_file']))

    log.info("autograph wrote signed_file %s to %s", from_, to)
    return to
async def sign_mar384_with_autograph_hash(context, from_, fmt, to=None):
    """Signs a hash with autograph, injects it into the file, and writes the result to arg `to` or `from_` if `to` is None.

    Args:
        context (Context): the signing context
        from_ (str): the source file to sign
        fmt (str): the format to sign with
        to (str, optional): the target path to sign to. If None, overwrite
            `from_`. Defaults to None.

    Raises:
        Requests.RequestException: on failure
        SigningScriptError: when no suitable signing server is found for fmt

    Returns:
        str: the path to the signed file

    """
    log.info(
        "sign_mar384_with_autograph_hash(): signing {} with {}... using autograph /sign/hash"
        .format(from_, fmt))
    if not utils.is_autograph_signing_format(fmt):
        raise SigningScriptError(
            "No signing servers found supporting signing format {}".format(
                fmt))
    cert_type = task.task_cert_type(context)
    servers = get_suitable_signing_servers(context.signing_servers,
                                           cert_type, [fmt],
                                           raise_on_empty_list=True)
    s = servers[0]
    to = to or from_

    hash_algo, expected_signature_length = 'sha384', 512

    # Add a dummy signature into a temporary file (TODO: dedup with mardor.cli do_hash)
    tmp = tempfile.TemporaryFile()
    with open(from_, 'rb') as f:
        add_signature_block(f, tmp, hash_algo)

    tmp.seek(0)

    with MarReader(tmp) as m:
        hashes = m.calculate_hashes()
    h = hashes[0][1]

    tmp.close()

    # build and run the hash signature request
    sign_req = [{"input": base64.b64encode(h).decode('ascii')}]
    log.debug(
        "signing mar with hash alg %s using the default autograph keyid for %s",
        hash_algo, s.user)

    url = "%s/sign/hash" % s.server

    async def make_sign_req():
        auth = HawkAuth(id=s.user, key=s.password)
        with requests.Session() as session:
            r = session.post(url, json=sign_req, auth=auth)
            log.debug("Autograph response: {}".format(
                r.text[:120] if len(r.text) >= 120 else r.text))
            r.raise_for_status()
            return r.json()

    sign_resp = await retry_async(make_sign_req)
    signature = base64.b64decode(sign_resp[0]['signature'])

    # Add a signature to the MAR file (TODO: dedup with mardor.cli do_add_signature)
    if len(signature) != expected_signature_length:
        raise SigningScriptError(
            "signed mar hash signature has invalid length for hash algo {}. Got {} expected {}."
            .format(hash_algo, len(signature), expected_signature_length))

    # use the tmp file in case param `to` is `from_` which causes stream errors
    tmp_dst = tempfile.NamedTemporaryFile(mode='w+b', delete=False)
    with open(tmp_dst.name, 'w+b') as dst:
        with open(from_, 'rb') as src:
            add_signature_block(src, dst, hash_algo, signature)

    shutil.copyfile(tmp_dst.name, to)
    os.unlink(tmp_dst.name)

    verify_mar_signature(cert_type, fmt, to)

    log.info("wrote mar with autograph signed hash %s to %s", from_, to)
    return to
Ejemplo n.º 28
0
def die(*args, **kwargs):
    raise SigningScriptError("dying!")
Ejemplo n.º 29
0
def context_die(*args, **kwargs):
    raise SigningScriptError("dying")