summaryrefslogtreecommitdiffstats
path: root/scripts/clean-keydir
blob: bd614324e78e23a03d1551e9c3b96d0a021064d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/bin/bash

# Copyright (c) 2012 Jonathan McDowell <noodles@earth.li>,
#              2019 Daniel Kahn Gillmor <dkg@fifthhorseman.net>
# GNU GPL; v2 or later
# Given a key directory, prune, clean, or minimize the keys

# "prune" just does basic cleanup on the file, without getting rid of
# any third-party signatures.

set -e

if [ -z "$1" ] || [ -z "$2" ]; then
	cat >&2 <<EOF
Usage: $0 [prune|launder|clean|minimal] dir
   prune: remove invalid parts
   launder: invoke GnuPG's merge logic to trim the key
   clean: prune and drop non-debian third-party certifications
   minimal: prune and remove *all* third-party certifications
EOF
	exit 1
fi

declare -a GPGOPTIONS=(--batch
                       --no-tty
                       --quiet
                       --no-options
                       --homedir=/dev/null
                       --trust-model=always
                       --fixed-list-mode
                       --with-colons
                       --export-options=no-export-attributes
                      )


if [ "$1" == prune ]; then
    GPGOPTIONS+=(--no-keyring
                 --import-options=import-export
                )
elif [ "$1" == launder ]; then
    # we are going to do something very ugly...
    # see https://dev.gnupg.org/T4421
    : pass
elif [ "$1" == clean ]; then
    # we need to include all the known keys so that we keep the
    # interlocking signatures
    make
    GPGOPTIONS+=(--no-default-keyring
                 --import-options=import-export,import-clean
                 --export-options=export-clean
                 --keyring "$(readlink -f output/keyrings/debian-keyring.gpg)"
                 --keyring "$(readlink -f output/keyrings/debian-nonupload.gpg)"
                 --keyring "$(readlink -f output/keyrings/debian-maintainers.gpg)"
                 --keyring "$(readlink -f output/keyrings/debian-role-keys.gpg)"
                 --keyring "$(readlink -f output/keyrings/emeritus-keyring.gpg)"
                )
elif [ "$1" == minimal ]; then
    GPGOPTIONS+=(--no-keyring
                 --import-options=import-export,import-minimal
                 --export-options=export-minimal
                )
else
    echo "Must specify prune, launder, clean or minimal; not $1" >&2
    exit 1
fi

if [ ! -d "$2" ]; then
	printf '%s is not a directory' "$2" >&2
	exit 1
fi

# takes name of transferable public key file as $1, emits the laundered key to file named $2
launder_tpk() {
    local interim="$(mktemp -d interim.XXXXXXX)"
    local success=false
    local key="$1"
    local output="$2"
    mkdir -p -m 0700 "$interim/gpg" "$interim/split"
    cat > "$interim/gpg/gpg.conf" <<EOF
batch
no-tty
quiet
no-options
trust-model always
fixed-list-mode
with-colons
export-options no-export-attributes
EOF
    if gpg --homedir "$interim/gpg" --import-options=import-minimal --status-file "$interim/status" --import < "$key" &&
            fpr="$(awk '{ if ($1 == "[GNUPG:]" && $2 == "IMPORT_OK" && $3 == "1") { print $4 } }' < "$interim/status")" &&
            [ -n "$fpr" ] &&
            gpg --homedir "$interim/gpg" --export | (cd "$interim/split" && gpgsplit) &&
            gpg --homedir "$interim/gpg" --delete-key "$fpr"; then
        local pub="$interim/split/000001-006.public_key"
        local uid=$(ls "$interim/split/"*.user_id | head -n1)
        local sig=$(printf '%s/split/%06d-002.sig' "$interim" $(( "$(echo "${uid##$interim/split/}" | sed -e 's_^0*__' -e 's_-.*$__')" + 1 )) )
        if [ -r "$pub" ] && [ -r "$uid" ] && [ -r "$sig" ]; then
            if cat "$pub" "$uid" "$sig" | gpg --homedir "$interim/gpg" --import &&
                    gpg --homedir "$interim/gpg" --import < "$key" &&
                    gpg --homedir "$interim/gpg" --output "$output" --export "$fpr"; then
                success=true
            else
                printf 'Merging failed for %s (fpr: %s)\n' "$key" "$fpr" >&2
            fi
        else
            printf 'Could not find minimal TPK for %s (fpr: %s)\n' "$key" "$fpr" >&2
        fi
    else
        printf 'failed to do initial import of %s\n' "$key" >&2
    fi
    rm -rf "$interim"
    [ $success = true ]
}

cd "$2"
for key in 0x*; do
    success=false
    if [ "$1" == launder ]; then
        if launder_tpk "$key" "$key.new"; then
            success=true
        fi
    else
        if gpg "${GPGOPTIONS[@]}" --output "$key.new" --import "$key"; then
            success=true
        fi
    fi
	if [ $success = true ] &&  [ -s $key.new ]; then
		OLDSIZE=$(stat -c "%s" "$key")
		NEWSIZE=$(stat -c "%s" "$key.new")
		if [ $OLDSIZE -gt $NEWSIZE ]; then
			echo "Cleaning $key [$OLDSIZE] -> [$NEWSIZE]"
			mv "$key.new" "$key"
                elif [ $OLDSIZE -eq $NEWSIZE ] && ! cmp --quiet "$key" "$key.new" ; then
                    printf "Packets were reordered in $key"
                    if [ "$1" == launder ]; then
                        echo " (but ignoring while doing launder: https://dev.gnupg.org/T4422)"
                    else
		        mv "$key.new" "$key"
                        echo
                    fi
		fi
	fi
	[ -e "$key.new" ] && rm "$key.new"
done

exit 0