Add instructions on creating an OpenPGP key. Also add a checkkey.sh

script that can be used to test keys for minimal security requirements.
A long list of people helped with this.  In particular, Michael Lucas
and David Wolfskill contributed many corrections and suggestions.  I
started the script based on the addkey.sh script, and David Wolfskill
worked it into something usable.  After more testing, the text can
suggest using it to check keys before committing them.

Reviewed by:	accounts@, postmaster@, mwl@, sbruno@, gavin@
This commit is contained in:
Warren Block 2013-12-27 22:57:39 +00:00
parent 61cf96e16b
commit 7621bec9f3
Notes: svn2git 2020-12-08 03:00:23 +00:00
svn path=/head/; revision=43392
3 changed files with 385 additions and 1 deletions
en_US.ISO8859-1/articles/committers-guide
share/pgpkeys

View file

@ -13,6 +13,10 @@ INSTALL_ONLY_COMPRESSED?=
SRCS= article.xml
IMAGES_LIB= callouts/1.png
IMAGES_LIB+= callouts/2.png
IMAGES_LIB+= callouts/3.png
URL_RELPREFIX?= ../../../..
DOC_PREFIX?= ${.CURDIR}/../../..

View file

@ -166,6 +166,124 @@
</itemizedlist>
</sect1>
<sect1 xml:id="pgpkeys">
<title>Open<acronym>PGP</acronym> Keys for &os;</title>
<para>Cryptographic keys conforming to the
Open<acronym>PGP</acronym>
(<emphasis>Pretty Good Privacy</emphasis>) standard are used by
the &os; project to authenticate committers. Messages carrying
important information like public <acronym>SSH</acronym> keys
can be signed with the Open<acronym>PGP</acronym> key to prove
that they are really from the committer. See
<link xlink:href="http://www.nostarch.com/pgp_ml.htm">PGP &amp;
GPG: Email for the Practical Paranoid by Michael Lucas</link>
and <link
xlink:href="http://en.wikipedia.org/wiki/Pretty_Good_Privacy"></link>
for more information.</para>
<sect2 xml:id="pgpkeys-creating">
<title>Creating a Key</title>
<para>If you do not yet have an Open<acronym>PGP</acronym> key,
or your key does not meet &os; security requirements, here we
show how to generate one.</para>
<para>Install
<filename role="package">security/gnupg</filename>. Enter
these lines in <filename>~/.gnupg/gpg.conf</filename> to set
minimum acceptable defaults:</para>
<programlisting>fixed-list-mode
keyid-format 0xlong
personal-digest-preferences SHA512 SHA384 SHA256 SHA224
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 BZIP2 ZLIB ZIP Uncompressed
use-agent
verify-options show-uid-validity
list-options show-uid-validity
sig-notation issuer-fpr@notations.openpgp.fifthhorseman.net=%g
cert-digest-algo SHA512</programlisting>
<para>Generate a key:</para>
<screen>&prompt.user; <userinput>gpg --gen-key</userinput>
gpg (GnuPG) 2.0.22; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Warning: using insecure memory!
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
Your selection? <userinput>1</userinput>
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) <userinput>2048</userinput> <co xml:id="co-pgp-bits"/>
Requested keysize is 2048 bits
Please specify how long the key should be valid.
0 = key does not expire
&lt;n&gt; = key expires in n days
&lt;n&gt;w = key expires in n weeks
&lt;n&gt;m = key expires in n months
&lt;n&gt;y = key expires in n years
Key is valid for? (0) <userinput>3y</userinput> <co xml:id="co-pgp-expire"/>
Key expires at Wed Nov 4 17:20:20 2015 MST
Is this correct? (y/N) <userinput>y</userinput>
GnuPG needs to construct a user ID to identify your key.
Real name: <userinput><replaceable>Chucky Daemon</replaceable></userinput> <co xml:id="co-pgp-realname"/>
Email address: <userinput><replaceable>notreal@example.com</replaceable></userinput>
Comment:
You selected this USER-ID:
"<replaceable>Chucky Daemon &lt;notreal@example.com&gt;</replaceable>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? <userinput>o</userinput>
You need a Passphrase to protect your secret key.</screen>
<calloutlist>
<callout arearefs="co-pgp-bits">
<para>2048-bit keys with a three-year expiration provide
adequate protection at present (2013-12). <link
xlink:href="http://danielpocock.com/rsa-key-sizes-2048-or-4096-bits"/> describes the situation in more detail.</para>
</callout>
<callout arearefs="co-pgp-expire">
<para>A three year key lifespan is short enough to obsolete
keys weakened by advancing computer power, but long enough
to reduce key management problems.</para>
</callout>
<callout arearefs="co-pgp-realname">
<para>Use your real name here, preferably matching that
shown on government-issued <acronym>ID</acronym> to make
it easier for others to verify your identity. Text that
may help others identify you can be entered in the
<literal>Comment</literal> section.</para>
</callout>
</calloutlist>
<para>After the email address is entered, a passphrase is
requested. Methods of creating a secure passphrase are
contentious. Rather than suggest a single way, here are some
links to sites that describe various methods: <link
xlink:href="http://world.std.com/~reinhold/diceware.html"></link>,
<link
xlink:href="http://www.iusmentis.com/security/passphrasefaq/"></link>,
<link xlink:href="http://xkcd.com/936/"></link>, <link
xlink:href=""></link>.</para>
<para>Protect your private key and passphrase. If either the
private key or passphrase may have been compromised or
disclosed, immediately notify
<email>accounts@FreeBSD.org</email> and revoke the key.</para>
<para>Committing the new key is shown in
<xref linkend="commit-list"/>.</para>
</sect2>
</sect1>
<sect1 xml:id="committer.types">
<title>Commit Bit Types</title>
@ -2000,7 +2118,8 @@ ControlPersist yes</screen>
<para>If you have been given commit rights to one or more of the
repositories:</para>
<itemizedlist>
<itemizedlist xml:id="commit-list">
<title>Steps for New Committers</title>
<listitem>
<para>Add your author entity to
<filename>head/share/xml/authors.ent</filename>; this

261
share/pgpkeys/checkkey.sh Executable file
View file

@ -0,0 +1,261 @@
#!/bin/sh
#
# $FreeBSD$
#
#
# This script is intended to sanity-check PGP keys used by folks with
# @FreeBSD.org email addresses. The checks are intended to be derived
# from the information at
# <https://we.riseup.net/riseuplabs+paow/openpgp-best-practices#openpgp-key-checks>
#
\unalias -a
progname=${0##*/}
# Print an informational message
info() {
echo "$@" >&2
}
# Print a warning message
warning() {
echo "WARNING: $@" >&2
}
# Print an error message and exit
error() {
echo "ERROR: $@" >&2
exit 1
}
# Print usage message and exit
usage() {
echo "usage: ${progname} [user] [keyid ...]\n" >&2
exit 1
}
# Look for gpg
gpg=$(which gpg)
if [ $? -gt 0 -o -z "${gpg}" -o ! -x "${gpg}" ] ; then
error "Cannot find gpg"
fi
# Set up our internal default gpg invocation options
_gpg() {
${gpg} \
--display-charset utf-8 \
--no-greeting \
--no-secmem-warning \
--keyid-format long \
--list-options no-show-uid-validity \
"$@"
}
# Look up key by key ID
getkeybyid() {
_gpg --with-colons --list-keys "$1" 2>/dev/null | awk -F: \
'$5 ~ /^\([0-9A-F]{8}\)?'"$1"'$/i && $12 ~ /ESC/ { print $5 }'
}
# Look up key by email
getkeybyemail() {
_gpg --with-colons --list-keys "$1" 2>/dev/null | awk -F: \
'$10 ~ /<'"$1"'>/i && $12 ~ /ESC/ { print $5 }'
}
# The first command-line argument can be a user name or a key ID.
if [ $# -gt 0 ] ; then
id="$1"
shift
else
id=$(id -nu)
warning "No argument specified, calculating user ID"
fi
# Now let's try to figure out what kind of thing we have as an ID.
# We'll check for a keyid first, as it's readily distinguishable
# from other things, but if we see that we have one, we push it back
# onto the argument list for later processing (becasue we may have
# been given a list of keyods).
if echo "${id}" | egrep -q '^[0-9A-F]{16}$'; then
id_type="keyid"
set -- "${id}" $@
elif echo "${id}" | egrep -q '^[0-9A-F]{8}$'; then
id_type="keyid"
set -- "${id}" $@
elif echo "${id}" | egrep -iq '^[a-z][-0-9a-z_]*@([-0-9a-z]+\.)[-0-9a-z]+$'; then
id_type="email"
email="${id}"
elif echo "${id}" | egrep -iq '^[a-z][-0-9a-z_]*$'; then
id_type="login"
login="${id}"
email="${id}@FreeBSD.org"
else
error "Cannot recognize type of ${id} (keyid, login, or email)"
fi
if [ $# -ne 0 ] ; then
# Verify the keys that were specified on the command line
for arg ; do
case $(expr "${arg}" : '^[0-9A-Fa-f]\{8,16\}$') in
8)
warning "${arg}: recommend using 16-digit keyid"
;&
16)
keyid=$(getkeybyid "${arg}")
if [ -n "${keyid}" ] ; then
keyids="${keyids} ${keyid}"
else
warning "${arg} not found"
fi
;;
*)
warning "${arg} does not appear to be a valid key ID"
;;
esac
shift
done
else
# Search for keys by freebsd.org email
keyids=$(getkeybyemail "${email}")
case $(echo "${keyids}" | wc -w) in
0)
error "no keys found for ${email}"
;;
1)
;;
*)
warning "Multiple keys found for <${email}>; checking all."
warning "If this is not what you want, specify a key ID" \
"on the command line."
;;
esac
fi
# :(
if [ -z "${keyids}" ] ; then
error "no valid keys were found"
fi
# add a problem report to the list of problems with this key
badkey() {
key_problems=" ${key_problems}$@
"
}
exitstatus=0
# Check the keys
for key in ${keyids} ; do
# no problems found yet
key_problems=""
IFS_save="${IFS}"
key_info=$( ${gpg} --no-secmem-warning --export-options export-minimal --export ${key} \
| ${gpg} --no-secmem-warning --list-packets )
# primary keys should be RSA or DSA-2
IFS=""
version=$( echo $key_info | \
awk '$1 == "version" && $3 == "algo" {sub(",", "", $2); print $2; exit 0}' )
IFS="${IFS_save}"
if [ $version -lt 4 ]; then
badkey "This key is a deprecated version $version key!"
fi
IFS=""
algonum=$( echo $key_info | \
awk '$1 == "version" && $3 == "algo" {sub(",", "", $4); print $4; exit 0}' )
IFS="${IFS_save}"
case ${algonum} in
"1") algo="RSA" ;;
"17") algo="DSA" ;;
"18") algo="ECC" ;;
"19") algo="ECDSA" ;;
*) algo="*UNKNOWN*" ;;
esac
IFS=""
bitlen=$( echo $key_info | \
awk -F : '$1 ~ "pkey" { gsub("[^0-9]*","", $2); print $2; exit 0}' )
IFS="${IFS_save}"
echo "key ${key}: ${algo}, ${bitlen} bits"
case ${algo} in
RSA) ;;
DSA) if [ "${bitlen}" -le 1024 ]; then \
badkey "DSA, but not DSA-2"; \
fi ;;
*) badkey "non-preferred algorithm"
esac
# self-signatures must not use MD5 or SHA1
IFS=""
sig_algonum=$( echo $key_info | \
awk '$1 == "digest" && $2 == "algo" {sub(",", "", $3); print $3; exit 0}' )
IFS="${IFS_save}"
case sig_algonum in
1) sigs="MD5";;
2) sigs="SHA1";;
3) sigs="RIPEMD160";;
8) sigs="SHA256";;
9) sigs="SHA384";;
10) sigs="SHA512";;
11) sigs="SHA224";;
*)
esac
for sig in ${sigs}; do
if [ "${sig}" = "MD5" -o "${sig}" = "SHA1" ]; then
badkey "self-signature ${sig}"
fi
done
# digest algo pref must include at least one member of SHA-2
# at a higher priority than both MD5 and SHA1
IFS=""
algopref=$( echo $key_info | \
awk -F : '$1 ~ "pref-hash-algos" {gsub("[^ 0-9]", "", $2); print $2; exit 0}' )
IFS="${IFS_save}"
# if 3, 2, or 1 are before 11, 10, 9, or 8, then
set -- ${algopref}
if [ $1 -lt 4 ]; then
badkey "algorithm prefs do not have SHA-2 higher than MD5 or SHA1"
fi
# primary keys should have an expiration date at least a year
# in the future to make them worth committing, but no more
# than three years in the future
expires=$( _gpg --list-keys ${key} | \
awk "/$keyid .*expires:/ {sub(\"[^-0-9]\", \"\", \$NF); print \$NF; exit 0}" )
if [ -z "${expires}" ]; then
badkey "this key does not expire"
else
expires_s=$( date -jf "%F" "+%s" "${expires}" )
now_s=$( date "+%s" )
# 86400 == # seconds in a normal day
expire_int_d=$(( ( ${expires_s} - ${now_s} ) / 86400 ))
exp_min=$(( 1 \* 365 )) # Min expiry time is 1 year
exp_max=$(( 3 \* 365 + 1 )) # Max expiry time is 3 years
# We add 1 day because in a 3-year
# period, probability of a leap day
# is 297/400, about 0.74250
if [ ${expire_int_d} -lt ${exp_min} ]; then
badkey "Key $key expires in less than 1 year ($expire_int_d days)"
fi
if [ ${expire_int_d} -gt ${exp_max} ]; then
badkey "Key $key expires in more than 3 years ($expire_int_d days)"
fi
fi
# report problems
if [ -z "${key_problems}" ]; then
echo " key okay, ${key} meets minimal requirements" >&2
else
exitstatus=1
echo " ** problems found:" >&2
echo "${key_problems}" >&2
echo " ** key ${key} should not be used!"
fi
echo
done
exit ${exitstatus}