# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
import sys
import pytest
import llnl.util.filesystem as fs
import spack.bootstrap
import spack.util.executable
import spack.util.gpg
from spack.main import SpackCommand
from spack.paths import mock_gpg_data_path, mock_gpg_keys_path
from spack.util.executable import ProcessError
#: spack command used by tests below
gpg = SpackCommand('gpg')
bootstrap = SpackCommand('bootstrap')
mirror = SpackCommand('mirror')
pytestmark = pytest.mark.skipif(sys.platform == "win32",
reason="does not run on windows")
[docs]@pytest.fixture
def tmp_scope():
"""Creates a temporary configuration scope"""
base_name = 'internal-testing-scope'
current_overrides = set(
x.name for x in
spack.config.config.matching_scopes(r'^{0}'.format(base_name)))
num_overrides = 0
scope_name = base_name
while scope_name in current_overrides:
scope_name = '{0}{1}'.format(base_name, num_overrides)
num_overrides += 1
with spack.config.override(spack.config.InternalConfigScope(scope_name)):
yield scope_name
# test gpg command detection
[docs]@pytest.mark.parametrize('cmd_name,version', [
('gpg', 'undetectable'), # undetectable version
('gpg', 'gpg (GnuPG) 1.3.4'), # insufficient version
('gpg', 'gpg (GnuPG) 2.2.19'), # sufficient version
('gpg2', 'gpg (GnuPG) 2.2.19'), # gpg2 command
])
def test_find_gpg(cmd_name, version, tmpdir, mock_gnupghome, monkeypatch):
TEMPLATE = ('#!/bin/sh\n'
'echo "{version}"\n')
with tmpdir.as_cwd():
for fname in (cmd_name, 'gpgconf'):
with open(fname, 'w') as f:
f.write(TEMPLATE.format(version=version))
fs.set_executable(fname)
monkeypatch.setitem(os.environ, "PATH", str(tmpdir))
if version == 'undetectable' or version.endswith('1.3.4'):
with pytest.raises(spack.util.gpg.SpackGPGError):
spack.util.gpg.init(force=True)
else:
spack.util.gpg.init(force=True)
assert spack.util.gpg.GPG is not None
assert spack.util.gpg.GPGCONF is not None
[docs]def test_no_gpg_in_path(tmpdir, mock_gnupghome, monkeypatch, mutable_config):
monkeypatch.setitem(os.environ, "PATH", str(tmpdir))
bootstrap('disable')
with pytest.raises(RuntimeError):
spack.util.gpg.init(force=True)
[docs]@pytest.mark.maybeslow
def test_gpg(tmpdir, tmp_scope, mock_gnupghome):
# Verify a file with an empty keyring.
with pytest.raises(ProcessError):
gpg('verify', os.path.join(mock_gpg_data_path, 'content.txt'))
# Import the default key.
gpg('init', '--from', mock_gpg_keys_path)
# List the keys.
# TODO: Test the output here.
gpg('list', '--trusted')
gpg('list', '--signing')
# Verify the file now that the key has been trusted.
gpg('verify', os.path.join(mock_gpg_data_path, 'content.txt'))
# Untrust the default key.
gpg('untrust', 'Spack testing')
# Now that the key is untrusted, verification should fail.
with pytest.raises(ProcessError):
gpg('verify', os.path.join(mock_gpg_data_path, 'content.txt'))
# Create a file to test signing.
test_path = tmpdir.join('to-sign.txt')
with open(str(test_path), 'w+') as fout:
fout.write('Test content for signing.\n')
# Signing without a private key should fail.
with pytest.raises(RuntimeError) as exc_info:
gpg('sign', str(test_path))
assert exc_info.value.args[0] == 'no signing keys are available'
# Create a key for use in the tests.
keypath = tmpdir.join('testing-1.key')
gpg('create',
'--comment', 'Spack testing key',
'--export', str(keypath),
'Spack testing 1',
'spack@googlegroups.com')
keyfp = spack.util.gpg.signing_keys()[0]
# List the keys.
# TODO: Test the output here.
gpg('list')
gpg('list', '--trusted')
gpg('list', '--signing')
# Signing with the default (only) key.
gpg('sign', str(test_path))
# Verify the file we just verified.
gpg('verify', str(test_path))
# Export the key for future use.
export_path = tmpdir.join('export.testing.key')
gpg('export', str(export_path))
# Test exporting the private key
private_export_path = tmpdir.join('export-secret.testing.key')
gpg('export', '--secret', str(private_export_path))
# Ensure we exported the right content!
with open(str(private_export_path), 'r') as fd:
content = fd.read()
assert "BEGIN PGP PRIVATE KEY BLOCK" in content
# and for the public key
with open(str(export_path), 'r') as fd:
content = fd.read()
assert "BEGIN PGP PUBLIC KEY BLOCK" in content
# Create a second key for use in the tests.
gpg('create',
'--comment', 'Spack testing key',
'Spack testing 2',
'spack@googlegroups.com')
# List the keys.
# TODO: Test the output here.
gpg('list', '--trusted')
gpg('list', '--signing')
test_path = tmpdir.join('to-sign-2.txt')
with open(str(test_path), 'w+') as fout:
fout.write('Test content for signing.\n')
# Signing with multiple signing keys is ambiguous.
with pytest.raises(RuntimeError) as exc_info:
gpg('sign', str(test_path))
assert exc_info.value.args[0] == \
'multiple signing keys are available; please choose one'
# Signing with a specified key.
gpg('sign', '--key', keyfp, str(test_path))
# Untrusting signing keys needs a flag.
with pytest.raises(ProcessError):
gpg('untrust', 'Spack testing 1')
# Untrust the key we created.
gpg('untrust', '--signing', keyfp)
# Verification should now fail.
with pytest.raises(ProcessError):
gpg('verify', str(test_path))
# Trust the exported key.
gpg('trust', str(export_path))
# Verification should now succeed again.
gpg('verify', str(test_path))
# Publish the keys using a directory path
test_path = tmpdir.join('dir_cache')
os.makedirs('%s' % test_path)
gpg('publish', '--rebuild-index', '-d', str(test_path))
assert os.path.exists('%s/build_cache/_pgp/index.json' % test_path)
# Publish the keys using a mirror url
test_path = tmpdir.join('url_cache')
os.makedirs('%s' % test_path)
test_url = 'file://%s' % test_path
gpg('publish', '--rebuild-index', '--mirror-url', test_url)
assert os.path.exists('%s/build_cache/_pgp/index.json' % test_path)
# Publish the keys using a mirror name
test_path = tmpdir.join('named_cache')
os.makedirs('%s' % test_path)
mirror_url = 'file://%s' % test_path
mirror('add', '--scope', tmp_scope, 'gpg', mirror_url)
gpg('publish', '--rebuild-index', '-m', 'gpg')
assert os.path.exists('%s/build_cache/_pgp/index.json' % test_path)