diff --git a/ChangeLog b/ChangeLog index f4c57b3..b99e6d0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2014-06-05 Niibe Yutaka + + * tool/add_openpgp_authkey_from_gpgssh.py: New. + 2014-04-17 Niibe Yutaka * src/muladd_256.h (MULADD_256_ASM): New. diff --git a/tool/add_openpgp_authkey_from_gpgssh.py b/tool/add_openpgp_authkey_from_gpgssh.py new file mode 100644 index 0000000..1eb80de --- /dev/null +++ b/tool/add_openpgp_authkey_from_gpgssh.py @@ -0,0 +1,189 @@ +""" +add_openpgp_authkey_from_gpgssh.py + +Copyright (C) 2014 Free Software Initiative of Japan +Author: NIIBE Yutaka + +This file is a part of Gnuk, a GnuPG USB Token implementation. + +Gnuk is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Gnuk is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +from gpg_agent import gpg_agent +from binascii import hexlify, unhexlify +from sexp import sexp +from time import time +from struct import pack, unpack +from hashlib import sha1, sha256 +import re + +ALGO_RSA=1 +DIGEST_SHA256=8 +OPENPGP_VERSION=4 + +def count_bits(mpi_bytes): + return ord(mpi_bytes[0]).bit_length()+(len(mpi_bytes)-1)*8 + +class rsa_key(object): + def __init__(self, timestamp, n, e): + self.__timestamp = timestamp + self.__n = n + self.__e = e + + def hash_pubkey_key(self, md): + hl = 6 + len(self.__n) + 2 + len(self.__e) + 2 + md.update(pack('>BHBLB', 0x99, hl, 4, self.__timestamp, ALGO_RSA)) + md.update(pack('>H', count_bits(self.__n)) + self.__n) + md.update(pack('>H', count_bits(self.__e)) + self.__e) + + def compose_public_subkey_packet(self): + psp = pack('>BLB', OPENPGP_VERSION, self.__timestamp, ALGO_RSA) + psp += pack('>H', count_bits(self.__n)) + self.__n + psp += pack('>H', count_bits(self.__e)) + self.__e + return '\xB9' + pack('>H', len(psp)) + psp + + def compute_keygrip(self): + md = sha1("\x00" + self.__n) + return md.digest() + + def compute_fpr(self): + md = sha1() + self.hash_pubkey_key(md) + return md.digest() + +def compose_binding_signature_packet(g, primary_key, subkey, sig_timestamp): + # Binding signature packet consists of: subpackets of hashed and unhashed + # (1) hashed subpacket: this subpacket is the target to calculate digest + sig_subp_hashed = pack('>B', 5) + '\x02' + pack('>L', sig_timestamp) + sig_subp_hashed += pack('>B', 2) + '\x1b' + '\x20' # Usage AUTH + # (2) unhashed subpacket: this subpacket is _not_ the target for digest + sig_subp_unhashed = pack('>B', 9) + '\x10' + primary_key.compute_fpr()[-8:] + # + md = sha256() + primary_key.hash_pubkey_key(md) + subkey.hash_pubkey_key(md) + # Start building binding signature packet, starting OPENPGP_VERSION... + sigp = pack('>BBBB', OPENPGP_VERSION, 0x18, ALGO_RSA, DIGEST_SHA256) + sigp += pack('>H', len(sig_subp_hashed)) + sig_subp_hashed + # And feed it to digest calculator + md.update(sigp) + md.update(pack('>BBL', OPENPGP_VERSION, 0xff, len(sig_subp_hashed)+6)) + digest = md.digest() + # Then, add unhashed subpacket and first two bytes of digest + sigp += pack('>H', len(sig_subp_unhashed)) + sig_subp_unhashed + sigp += digest[0:2] + print("Digest 2-byte: %s" % hexlify(digest[0:2])) + # Ask signing to this digest by the corresponding secret key to PRIMARY_KEY + signature = do_sign(g, primary_key, DIGEST_SHA256, digest) + # Then, add the signature to the binding signature packet + sigp += pack('>H', count_bits(signature)) + signature + # Prepending header, it's the binding signature packet + return '\x89' + pack('>H', len(sigp)) + sigp + +def build_rsakey_from_ssh_key_under_gpg_agent(g, timestamp=None): + # (1) Get the list of available key specifying '--with-ssh' + g.send_command("KEYINFO --list --with-ssh --data\n") + kl_str = g.get_response() + kl_str = kl_str[0:-1] + kl = kl_str.split('\n') + # (2) Select SSH key(s) + kl_ssh = [kg for kg in kl if re.search("S$", kg)] # Select SSH key + # (3) Use the first entry of the list (in future, use all???) + print("KEYINFO: %s" % kl_ssh[0]) + # KG: The keygrip of key in question + kg = kl_ssh[0].split(' ')[0] + # By READKEY command, get the public key information of KG + g.send_command("READKEY %s\n" % kg) + pubkey_info_str = g.get_response() + # The information is in SEXP format, extract N and E + s = sexp(pubkey_info_str) + if s[0] != 'public-key': + print s + exit(1) + rsa = s[1] + if rsa[0] != 'rsa': + print rsa + exit(1) + n_x = rsa[1] + if n_x[0] != 'n': + print n_x + exit(1) + n_byte_str = n_x[1] + while n_byte_str[0] == '\x00': + n_byte_str = n_byte_str[1:] + n = n_byte_str + e_x = rsa[2] + if e_x[0] != 'e': + print e_x + exit(1) + e = e_x[1] + if not timestamp: + timestamp = int(time()) + # Compose our RSA_KEY by TIMESTAMP, N, and E + return rsa_key(timestamp,n,e) + +BUFSIZE=1024 +def build_rsakey_from_openpgp_file(filename): + f = open(filename, "rb") + openpgp_bytes = f.read(BUFSIZE) + f.close() + header_tag, packet_len, version, timestamp, algo, n_bitlen = \ + unpack('>BHBLBH', openpgp_bytes[:11]) + if header_tag != 0x99: + print ("openpgp: 0x99 expected (0x%02x)" % header_tag) + exit(1) + n_len = (n_bitlen + 7) / 8 + n = openpgp_bytes[11:11+n_len] + e_bitlen = unpack('>H', openpgp_bytes[11+n_len:11+n_len+2])[0] + e_len = (e_bitlen + 7) / 8 + e = openpgp_bytes[11+n_len+2:11+n_len+2+e_len] + return rsa_key(timestamp,n,e) + +def do_sign(g, pubkey, digest_algo, digest): + g.send_command('SIGKEY %s\n' % hexlify(pubkey.compute_keygrip())) + if digest_algo == DIGEST_SHA256: + g.send_command('SETHASH --hash=sha256 %s\n' % hexlify(digest)) + else: + raise('Unknown digest algorithm', digest_algo) + g.send_command('PKSIGN\n') + sig_result_str = g.get_response() + sig_sexp = sexp(sig_result_str) # [ "sig-val" [ "rsa" [ "s" "xxx" ] ] ] + return sig_sexp[1][1][1] + +import sys + +if __name__ == '__main__': + # + filename = sys.argv[1] + # Connect to GPG-agent: + g = gpg_agent() + print("GPG-agent says: %s" % g.read_line()) + # + primary_key = build_rsakey_from_openpgp_file(filename) + print("Primary key fingerprint: %s" % hexlify(primary_key.compute_fpr())) + print("Primary keygrip: %s" % hexlify(primary_key.compute_keygrip())) + # + subkey = build_rsakey_from_ssh_key_under_gpg_agent(g) + print("Subkey fingerprint: %s" % hexlify(subkey.compute_fpr())) + print("Subkey keygrip: %s" % hexlify(subkey.compute_keygrip())) + # + openpgp_subkey_packet = subkey.compose_public_subkey_packet() + openpgp_sig_packet = compose_binding_signature_packet(g, primary_key, subkey, int(time())) + # Query to GPG-agent finished + g.close() + # Append OpenPGP packets to file + f = open(filename, "ab") + f.write(openpgp_subkey_packet) + f.write(openpgp_sig_packet) + f.close()