forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
6.8 KiB
189 lines
6.8 KiB
""" |
|
add_openpgp_authkey_from_gpgssh.py |
|
|
|
Copyright (C) 2014 Free Software Initiative of Japan |
|
Author: NIIBE Yutaka <[email protected]> |
|
|
|
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 <http://www.gnu.org/licenses/>. |
|
""" |
|
|
|
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()
|
|
|