ACC SHELL

Path : /proc/self/root/usr/share/YaST2/modules/
File Upload :
Current File : //proc/self/root/usr/share/YaST2/modules/GPG.ycp

/**
 * File:	modules/GPG.ycp
 * Package:	yast2
 * Summary:	A wrapper for gpg binary
 * Authors:	Ladislav Slezák <lslezak@suse.cz>
 *
 * $Id: GPG.ycp 60336 2010-01-12 14:18:15Z lslezak $
 *
 * This module provides GPG key related functions. It is a wrapper around gpg
 * binary. It uses caching for reading GPG keys from the keyrings.
 */

{

import "String";
import "Report";
import "FileUtils";

module "GPG";

textdomain "base";


// value for --homedir gpg option, empty string means default home directory
string home = "";

// key cache
list<map> public_keys = nil;
// key cache
list<map> private_keys = nil;

/**
 * (Re)initialize the module, the cache is invalidated if the home directory is changed.
 * @param home_dir home directory for gpg (location of the keyring)
 * @param force unconditionaly clear the key caches
 */
global boolean Init(string home_dir, boolean force)
{
    if (FileUtils::IsDirectory(home_dir) != true)
    {
	y2error("Path %1 is not a directory", home_dir);
	return false;
    }

    if (home_dir != home || force)
    {
	// clear the cache, home has been changed
	public_keys = nil;
	private_keys = nil;
    }

    home = home_dir;

    return true;
}

/**
 * Build GPG option string
 * @param options additional gpg options
 * @return string gpg option string
 */
string buildGPGcommand(string options)
{
    string home_opt = (size(home) > 0) ? sformat("--homedir '%1' ", String::Quote(home)) : "";
    string ret = "gpg " + home_opt + options;
    y2milestone("gpg command: %1", ret);

    return ret;
}

/**
 * Execute gpg with the specified parameters, --homedir is added to the options
 * @param options additional gpg options
 * @return map result of the execution
 */
map callGPG(string options)
{
    map ret = $[];
    string command = "LC_ALL=C " + buildGPGcommand(options);

    ret = (map) SCR::Execute(.target.bash_output, command);

    if (ret["exit"]:-1 != 0)
    {
	y2error("gpg error: %1", ret);
    }

    return ret;
}

/**
 * Map for parsing gpg output. Key is regexp, value is the key returned
 * in the result of the parsing.
 */
map<string,string> parsing_map = $[
    // secret key ID
    "^sec  .*/([^ ]*) " : "id",
    // public key id
    "^pub  .*/([^ ]*) " : "id",
    // user id
    "^uid *(.*)"	: "uid",
    // fingerprint
    "^      Key fingerprint = (.*)" : "fingerprint"
];

/**
 * Parse gpg output using the parsing map
 * @param lines gpg output (splitted into lines)
 * @return map parsed output
 */
map parse_key(list<string> lines)
{
    map ret = $[];

    foreach(string line, lines,
	{
	    foreach(string regexp, string key, parsing_map,
		{
		    string parsed = regexpsub(line, regexp, "\\1");

		    if (parsed != nil)
		    {
			// there might be more UIDs
			if (key == "uid")
			{
			    y2milestone("%1: %2", key, parsed);
			    ret[key] = add(ret[key]:[], parsed);
			}
			else
			{
			    if (haskey(ret, key))
			    {
				y2warning("Key %1: replacing old value '%2' with '%3'", key, ret[key]:"", parsed);
			    }
			    ret[key] = parsed;
			}
		    }
		}
	    );
	}
    );

    y2milestone("Parsed key: %1", ret);

    return ret;
}

/**
 * Parse gpg output
 * @param input gpg output
 * @return list<map> parsed keys
 */
list<map> parseKeys(string input)
{
    // note: see /usr/share/doc/packages/gpg/DETAILS for another way

    list<map> ret = [];
    list<string> lines = splitstring(input, "\n");

    if (size(input) > 2)
    {
	// remove the header
	lines = remove(lines, 0);
	lines = remove(lines, 0);
    }

    list<list<string> > key_lines = [];
    list<string> key_line_list = [];

    // create groups
    foreach(string line, lines,
	{
	    if (line == "")
	    {
		key_lines = add(key_lines, key_line_list);
		key_line_list = [];
	    }
	    else
	    {
		key_line_list = add(key_line_list, line);
	    }
	}
    );

    // not needed anymore, save some memory
    lines = [];

    // parse each group to map
    foreach(list<string> keylines, key_lines,
	{
	    map parsed = parse_key(keylines);

	    if (size(parsed) > 0)
	    {
		ret = add(ret, parsed);
	    }
	}
    );

    y2milestone("Parsed keys: %1", ret);

    return ret;
}

/**
 * Return list of the public keys in the keyring.
 * @return list<map> public keys: [ $["fingerprint": string key_fingerprint, "id": string key_ID, "uid": list<string> user_ids], ...]
 */
global list<map> PublicKeys()
{
    // return the cached values if available
    if (public_keys != nil)
    {
	return public_keys;
    }

    map out = callGPG("--list-keys --fingerprint");

    if (out["exit"]:-1 == 0)
    {
	public_keys = parseKeys(out["stdout"]:"");
    }

    return public_keys;
}

/**
 * Return list of the private keys in the keyring.
 * @return list<map> public keys: [ $["fingerprint": string key_fingerprint, "id": string key_ID, "uid": list<string> user_ids], ...]
 */
global list<map> PrivateKeys()
{
    // return the cached values if available
    if (private_keys != nil)
    {
	return private_keys;
    }

    map out = callGPG("--list-secret-keys --fingerprint");

    if (out["exit"]:-1 == 0)
    {
	private_keys = parseKeys(out["stdout"]:"");
    }

    return private_keys;
}

/**
 * Create a new gpg key. Executes 'gpg --gen-key' in an xterm window (in the QT UI)
 * or in the terminal window (in the ncurses UI).
 */
global boolean CreateKey()
{
    string xterm = "/usr/bin/xterm";
    string command = buildGPGcommand("--gen-key");
    boolean text_mode = UI::GetDisplayInfo()["TextMode"]:false;

    y2debug("text_mode: %1", text_mode);

    boolean ret = false;

    if (!text_mode)
    {
	if (SCR::Read(.target.size, xterm) < 0)
	{
	    // TODO FIXME
	    Report::Error(_("Xterm is missing, install xterm package."));
	    return false;
	}

	string exit_file = ((string)SCR::Read(.target.tmpdir)) + "/gpg_tmp_exit_file";
	if (FileUtils::Exists(exit_file))
	{
	    SCR::Execute(.target.execute, "rm -f " + exit_file);
	}

	command = "LC_ALL=C " + xterm + " -e \"" + command + "; echo $? > " + exit_file + "\"";
	y2internal("Executing: %1", command);

	// in Qt start GPG in a xterm window
	SCR::Execute(.target.bash, command);

	if (FileUtils::Exists(exit_file))
	{
	    // read the exit code from file
	    // (the exit code from the SCR call above is the xterm exit code which is not what we want here)
	    string exit_code = (string)SCR::Read(.target.string, exit_file);
	    y2milestone("Read exit code from tmp file %1: %2", exit_file, exit_code);

	    ret = exit_code == "0\n";
	}
	else
	{
	    y2warning("Exit file is missing, the gpg command has failed");
	    ret = false;
	}
    }
    else
    {
	command = "LC_ALL=C " + command;
	y2internal("Executing in terminal: %1", command);
	// in ncurses use UI::RunInTerminal
	ret = UI::RunInTerminal(command) == 0;
    }

    if (ret)
    {
	// invalidate cache, force reloading
	Init(home, true);
    }

    return ret;
}

/**
 * Sign a file. The ASCII armored signature is stored in file with .asc suffix
 * @param keyid id of the signing key
 * @param file the file to sign
 * @param passphrase passphrase to unlock the private key
 * @param ascii_signature if true ASCII armored signature is created
 *        (with suffix .asc) otherwise binary signature (with suffix .sig) is created
 * @return boolean true if the file has been successfuly signed
 */
boolean SignFile(string keyid, string file, string passphrase, boolean ascii_signature)
{
    if (passphrase == nil || keyid == nil || keyid == "" || file == nil || file == "")
    {
	y2error("Invalid parameters: keyid: %1, file: %2, passphrase: %3", keyid, file, passphrase);
	return false;
    }

    // signature suffix depends on the format
    string suffix = ascii_signature ? ".asc" : ".sig";

    if ((integer)SCR::Read(.target.size, file + suffix) >= 0)
    {
	// remove the existing key
	SCR::Execute(.target.bash, sformat("rm -f '%1%2'", String::Quote(file), suffix));
    }

    // save the passphrase to a file
    string tmpfile = (string)SCR::Read(.target.tmpdir) + "/stdin";

    boolean written = (boolean)SCR::Write(.target.string, tmpfile, passphrase + "\n");

    if (!written)
    {
	return false;
    }

    // use the passphrase
    map out = callGPG(sformat("--detach-sign -u '%1' --no-tty --batch --command-fd=0 --passphrase-fd 0 %2 '%3' < '%4'", String::Quote(keyid), (ascii_signature ? "-a" : ""), String::Quote(file), String::Quote(tmpfile)));

    return out["exit"]:-1 == 0;
}

/**
 * Sign a file. The ASCII armored signature is stored in file with .asc suffix
 * @param keyid id of the signing key
 * @param file the file to sign
 * @param passphrase passphrase to unlock the private key
 * @return boolean true if the file has been successfuly signed
 */
global boolean SignAsciiDetached(string keyid, string file, string passphrase)
{
    return SignFile(keyid, file, passphrase, true);
}

/**
 * Sign a file. The binary signature is stored in file with .sig suffix
 * @param keyid id of the signing key
 * @param file the file to sign
 * @param passphrase passphrase to unlock the private key
 * @return boolean true if the file has been successfuly signed
 */
global boolean SignDetached(string keyid, string file, string passphrase)
{
    return SignFile(keyid, file, passphrase, false);
}

/**
 * Verify a file using a signature file. The key which has been used for signing must be imported in the keyring.
 * @param sig_file file with the signature
 * @param file file to verify
 * @return boolean true if the file has been successfuly verified
 */
global boolean VerifyFile(string sig_file, string file)
{
    map out = callGPG(sformat("--verify '%1' '%2'", String::Quote(sig_file), String::Quote(file)));

    return out["exit"]:-1 == 0;
}

/**
 * Export a public gpg key in ACSII armored file.
 * @param keyid id of the key
 * @param file the target file
 * @return boolean true if the file has been successfuly signed
 */
global boolean ExportAsciiPublicKey(string keyid, string file)
{
    map out = callGPG(sformat("-a --export '%1' > '%2'", String::Quote(keyid), String::Quote(file)));

    return out["exit"]:-1 == 0;
}

/**
 * Export a public gpg key in binary format.
 * @param keyid id of the key
 * @param file the target file
 * @return boolean true if the file has been successfuly signed
 */
global boolean ExportPublicKey(string keyid, string file)
{
    map out = callGPG(sformat("--export '%1' > '%2'", String::Quote(keyid), String::Quote(file)));

    return out["exit"]:-1 == 0;
}

}

ACC SHELL 2018