►
Description
OpenPGP is best known for its use in email encryption. But, OpenPGP is, perhaps more importantly, used to secure software updates, sign commits, and protect backups.
Historically, OpenPGP has been hard for both end-users and developers to use--we know, we actually worked on GnuPG! In Sequoia, we are trying to change that. Sequoia is a new OpenPGP implementation that places as much emphasis on usability as on security.
Sequoia is also written in Rust. This talk will focus on the challenges that we've faced using Rust--infinite types, streaming iterators, designing a clean API--and our solutions.
https://rome.rustfest.eu/sessions/sequoia
https://media.ccc.de/v/rustfest-rome-6-sequoia
A
For
that
introduction
today,
I'm
here
to
talk
about
Sequoia,
which
is
a
new
open,
PGP
implementation,
most
people
when
they
hear
open
PGP,
they
think
about
canoe,
PG
and
we've
learned
from
that,
and
we
have
a
new
implementation
and
it's
in
rust
and
I
want
to
tell
you
about
some
of
our
experiences,
trying
to
implement
open
PGP
in
rust,
and
this
talk
is
actually
done
with
my
colleague
Eustace
winter
who's
down
there
and
it's
a
from
both
of
us,
basically
so
Sequoia.
What
is
Sequoia.
A
The
first
commit
was
done
in
October
16th
2017,
so
the
project
is
just
over
a
year
old
and
we
decided
to
do
a
new,
open,
PGP
implementation
for
a
number
of
reasons,
but
kind
of
the
primary
motivator
was
based
on
our
experience
using
a
new
PG.
So
we
actually
hacked
on
canoe
PT
for
several
years,
and
we
discovered
that
canoe
PT
is
actually
kind
of
hard
to
modify.
The
project
itself
is
now
21.
Almost
22
years
old,
the
code
has
grown
organically.
A
There
aren't
that
many
unit
tests
and
one
thing
we
observed
that
there's
a
lot
of
tight
component
coupling
talking
to
developers
we've
discovered
that
canoe
PG
API
isn't
so
easy
to
use
there
they're
unsatisfied
with
it.
It
doesn't
do
exactly
what
they
want
and
they
end
up
writing
a
lot
of
code
in
order
to
work
around
the
new
peachy's
idea
of
how
things
should
be
done,
and
one
of
the
major
reasons
we
decided
to
use
rust
is
its
safety
properties
and
particular
memory
safety.
A
So
we
thought
it
was
irresponsible
to
do
a
new
security,
sensitive
tool
and
a
language
that
didn't
offer
the
type
of
things
that
rust
offered.
And
finally,
there
was
a
non-technical
reason
to
consider
doing
a
new,
open,
PGP
implementation,
and
that's
because
canoe
PG
is
under
the
GPL
and
unfortunately,
you
can't
use
GPL
software
in
the
iOS
store
or
the
iOS
App
Store.
A
So
who
are
we
who's
working
on
this
project?
There's
three
of
us
who
are
working
on
it
more
or
less
full-time
and
they're
down
here.
So
just
this
and
also
Kai,
and
we
all
were
former
new
PG
developers.
We
worked
there
for
two.
Two
and
a
half
years
and
since
the
fall
of
2017,
so
since
the
project
started,
we've
been
at
pep
and
pep
stands
for
a
pretty
easy
privacy
and
pep
is
working
on
tools
to
make
in
particular,
email
encryption
easy
to
use
for
everyone.
A
Our
funding
comes
primarily
from
pep,
which
is
there's
a
company
aspect
and
also
a
foundation,
and
the
wall
hall
and
staff
dome
is
also
providing
some
funding,
and
one
thing
that
we
hope
to
do
in
the
middle
term
is
actually
diversify.
Our
funding
base
so
we're
all
at
the
pep
foundation
and
it'll
be
nice.
If
there
were
other
organizations
involved
as
well.
A
So
that's
my
introduction
to
Sequoia,
but
what
is
open
PGP,
so
I've
talked
to
a
few
people
here
today
and
although
many
people
know
about
can
UPG,
there
are
some
people
who
aren't
that
familiar
with
it.
So
I
want
to
give
you
a
very
brief
introduction.
It's
a
standard
for
encryption,
data,
authentication
and
integrity
and
the
standard
is
was
produced
by
the
IETF
RFC
4880
and
although
people
associate
open
PGP
with
email,
it's
not
just
for
email
use
for
lots
of
things
and
it's
actually
a
core
part
of
the
internet.
A
So
it's
used
for
package
signing
whenever
you
download
a
package
from
a
Debian
archive
apt
automatically
checks
to
make
sure
that
the
signature
is
good,
commit
signing
document,
signing
backups
archives,
encrypted
storage
in
the
cloud
encrypted
sneakernet.
So
if
you
have
a
USB
stick
and
you
want
to
encrypt
some
files,
then
bring
them
someplace
else.
You
can't
use
something
like
signal
for
that.
You
need
something
like
a
new
key
chain.
There
are
password
managers
that
use
open
PGP.
You
can
do
remote
authentication
using
open
PGP.
A
So
it's
a
very
general
purpose
standard
and
it's
a
packet
based
format.
So
an
open,
PGP
message
is
composed
of
a
number
of
packets.
In
a
certain
sense,
you
can
compose
them
in
an
arbitrary
manner,
but
the
standard
specifies
a
grammar,
so
it's
not
as
flexible
as
many
people
make
it
out
to
be.
When
you
have
some
data
that
you
want
to
encrypt
of
something
you
first
pack,
it
into
a
container
called
a
literal
data
packet.
A
It's
used
for
backups,
as
I
said
before,
backups
can
be
gigabytes
or
even
terabytes,
large,
obviously
doesn't
fit
in
memory,
and
so
somehow
you
have
to
be
able
to
stream
it
not
only
when
you're
doing
the
verification
process,
but
also
when
you're
generating
it,
and
this
enables
that
streaming
mechanism
there's
a
compression
container,
a
so-called
Sipe
container,
which
is
stands
for
symmetrically
encrypted
data
packet.
So,
like
most
crypto
schemes,
you
don't
use
public
key
encryption
to
encrypt
large
amounts
of
data.
You
generate
a
session
key.
A
You
use
something
like
a
EES
to
encrypt
the
bulk
data
and
then
you
only
encrypt
the
session
key
using
the
very
slow
and
expensive
public
key
cryptography.
So
let's
look
at
an
open,
PGP
message
in
detail.
Let's
say
we
want
to
say
hello
to
someone,
so
we
take
our
data
and
we
encapsulate
it
in
an
open,
PGP
packet,
in
particular
a
literal
data
packet.
So
now
we
don't
just
have
some
random
data,
it
has
a
format.
There's
this
container
format,
literal
data,
and
then
we
want
to
sign
it.
A
So
we
stick
this
one
path,
signature
in
the
front
and
the
signature
at
the
end,
and
then
we
encrypt
it.
We
put
the
whole
thing
in
a
cite
container
and
at
the
front
we
have
the
PK
Esk,
which
is
used
for
containing
the
encrypted
session
key,
and
if
you
think
about
what
I
just
described,
it
functions
a
little
bit
like
a
pipe.
A
So
you
take
your
data,
you
pipe
it
through
a
literal
data
container
type
thing:
you
pipe
that
into
a
significant
generation
type
thing
and
you
pipe
that
into
a
encryption
type
thing
and
by
the
way,
open.
Pgp
messages
are
also
used
for
key
exchange.
So
if
you
want
to
send
somebody
a
key
like
your
key
in
particular,
you
would
also
use
an
open
PGP
message
and
there's
a
public
key
packet,
a
public
sub
key
packet,
user,
ID
pack
and
a
bunch
of
others
that
aren't
quite
so
important.
A
So
that's
an
introduction
to
open,
PGP
and
now
I
want
to
get
into
our
implementation
and
I.
Don't
want
to
show
you
too
much
of
our
API,
but
what
I
want
to
show
you
instead
is
sort
of
the
challenges
that
we
faced,
trying
to
implement
Sequoia
in
rust,
and
there
are
five
major
challenges
that
we
encountered
and
then
I'm
going
to
present
today.
So
we
have
this
this
pipeline
that
we
want
to
process,
and
you
can
imagine
that
you
have
the
application
on
top
and
it's
reading
from
some
sort
of
reader.
A
A
Now
the
hashed
reader,
which
is
used
for
generating
the
signature,
isn't
technically
transforming
the
data,
but
it
is
doing
some
sort
of
computations
and
so
visualizing
it
as
a
pipe
is
still
useful.
Conceptually
and
here
we
see
that
there
are
kind
of
three
elements
in
our
pipe,
but
in
practice
there
are
other
things
that
you're
worried
about,
for
instance
framing,
so
you
want
to
enforce
packet
boundaries.
A
So
the
first
thing
that
you
do
when
you
start
using
open,
PGP
or
start
writing
an
open,
PGP
implementation
is
that
you
want
to
somehow
parse
a
message
and
before
coming
up
with
some
sort
of
crazy
API,
the
message
looks
a
little
bit
like
a
tree.
So
you
kind
of
start
implementing
a
parser.
You
do
a
depth-first
traversal
of
the
open
PGP
message.
This
looks
a
little
bit
like
the
visitor
pattern
and
it
basically
all
happens
on
the
stack
and
when
you
visit
each
packet,
then
you
can
imagine
calling
some
sort
of
callback.
A
So
here's
the
basic
idea:
we
have
a
compressed
data
packet
and
at
the
very
top
we
have
a
parse
function
and
we
read
the
the
algorithm.
We
generate
a
compressed
packets
and
push
the
filter
on
to
our
reader
stack
and
then
we
call
parse,
which
is
our
general-purpose
Luxor
for
processing
any
packet,
because
a
compressed
packet
can
contain
any
other
type
of
packet.
A
When
you
try
to
compile
that
Russ
complains,
it
says
you've
reached
the
recursion
limit,
while
instantiating
there's
really
really
really
long
pipe
types.
So
what
happened
there?
Well,
it
turns
out
that
rust
isn't
able
to
recognize
base
cases
when
you
do
an
recursion
with
the
type
system
and
we
have
the
generic
parser
calling
the
compressed
data
packet
parser,
which
in
turn
calls
the
generic
parser.
A
And
so
we
get
that
really
long
type
and
there's
kind
of
the
obvious
thing,
which
is
that
well,
okay,
we
we
need
to
articulate
a
base
case,
but
we
can't
tell
the
compiler
that
there's
no
way
to
express
that
in
rust.
Right
now,
but
there's
also
this
other
aspect,
which
is
kind
of
interesting
and
insightful
I,
think,
which
is
that
generics
can
result
in
a
lot
of
kind
of
invisible
types
that
maybe
you
don't
actually
need
in
practice,
because
in
general
nobody's
going
to
create
16
levels
of
compression
containers.
A
It's
just
going
to
make
your
message
bigger
and
bigger,
and
in
order
to
work
around
this,
you
need
to
use
dynamic
dispatch,
and
so,
if
you've
been
in
the
rest
community
and
when
you
start
learning
rust,
you
hear
action
erics
generics.
They
solve
all
your
problems
and
they're
super
fast.
But
you
really
do
sometimes
want
dynamic
dispatch
and
avoiding
boxing
is.
A
Oftentimes
completely
unnecessary
and
only
results
in
more
problems,
so
we
need
to
think
about
a
different
way
to
do
the
parsing
and
we
want
to
use
some
sort
of
dynamic
dispatch
in
order
to
create
these
filters,
so
we
create
a
generic
reader
stack.
So
what
that
means
is
that
we're
operating
on
some
sort
of
reader
and
we
don't
actually
know
what
the
concrete
type
is
and
then
we're
done
parsing
it
and
we
need
to
pop
off
the
current
container
and
continue
working.
A
So
we
need
something
like
into
inner,
but
due
to
type
erasure,
we
don't
actually
know
what
the
concrete
type
is
right.
We
have
some
sort
of
dynamic
dispatch
going
on,
so
how
can
we
recover
the
inner?
So
if
you've
looked
at
a
bunch
of
rust
types
that
do
this,
the
type
of
encapsulation,
then
the
into
in
our
method,
is
usually
implemented
on
the
concrete
type,
but
we
needed
for
trait
objects.
But
trait
objects
are
unsigned
right
when
you
think
into
inner.
A
So
this
is
a
discovery
that
I
found
after
looking
at
the
documentation
for
a
long
time
and
chatting
on
IRC,
and
it
was
a
very
insightful
thing
for
me
and
I
hope.
It's
also
a
little
bit
insightful
for
you.
So
we
go
ahead
and
we
take
self
and
it's
a
box,
and
now
we
can
work
with
our
trade
object
and
in
this
particular
API
we
don't
just
return
the
inner.
A
We
turn
an
option
inner
and
this
is
needed
in
order
to
handle
the
recursion
base
case,
where
you're
reading,
for
instance,
from
a
file
and
of
course
a
file
has
no
inner,
but
working
with
box
objects
is
ugly
right.
I'm
asking
that
you
have
a
reader
and
it's
on
the
stack.
Why
do
you
have
to
box
it
if
it's
just
on
the
stack?
So
if
all
of
your
API
require
is
boxed
reader
or
bufferedreader
as
we
call
it,
then
you're
doing
this
boxing
and
unboxing,
and
it's
just
kind
of
it's
not
terribly
agronomic.
A
You
can
use
a
transparent
forwarder,
so
you
do
implement
bufferedreader
for
boxed
bufferedreader
and
then
you
take
self.
You
do
as
ref
as
a
ref
is
a
neat
little
function
that
takes
a
box
and
turns
it
into
a
reference
and
that
you
can
call
the
actual
method
that
you
want
to
call
on
the
inner
thing
or
not
in
the
inner
thing.
But
on
the
box
thing-
and
this
happens
without
actually
having
a
concrete
type.
It's
all
dynamic
dispatch.
A
And
so
now
we
can
pass
a
box
bufferedreader
wherever
a
bufferedreader
is
needed,
which
means
that
we
can
simplify
our
API
right
because
our
API
doesn't
say
boxed
buffered
reader,
says
bufferedreader,
and
if
we
have
a
box
buffer
drita,
we
just
pass
it
in
and
we
can
also
pass
in
normal
buffered
readers.
So
that's
cool,
but
it
creates
a
linked
list.
A
So
let's
look
at
our
API
a
little
bit
more
closely.
We
have
a
new
method
which
we
use
for
constructing,
in
this
case
a
bufferedreader
limiter,
which
is
used
to
make
sure
that
you
can
only
read
so
many
bytes
from
a
particular
buffered
reader.
So
we
pass
in
a
reader
and
since
we
have
this
nice,
transparent
florid
or
we
can
also
passed
in
a
boxed
buffered
reader,
and
then
we
have
into
inner
well
into
an
ER.
Take
the
box
self
and
returns
the
inner
self.
A
Now
the
inner
self
is
an
R
and
R
is
a
bufferedreader
or
something
an
implement
bufferedreader
and
due
to
the
return
type
we're
forced
to
box
it.
So
we
have
to
do
box
R,
but
if
R
happened
to
be
already
boxed,
we
now
have
two
boxes
and
if
you
pop
things
on
the
stack
and
then
pop
them
off
and
pop
something
else
in
the
stack
and
pop
it
off,
and
you
do
that
a
whole
bunch
of
times,
you
now
have
n
boxes.
A
A
So
now
we
have
our
bufferedreader
interface.
How
are
we
gonna
do
a
better
parser
interface,
because
this
visitor
pattern
is
just
not
what
we
want
right
that
we
don't
want
to
use
callbacks.
It's
not
rust,
like
we
want
to
get
a
radar-like
API
and,
as
I
already
said,
it's
very
important
that
our
API
supports
streaming,
because
we
want
to
be
able
to
parse
this
these
parse,
these
big
messages.
So
how
do
we
do
an
iterator
interface?
Well,
what
if
we
just
use
the
iterator
interface
that
russ
provides?
A
A
Well,
if
we're
working
with
the
packet
itself,
the
thing
that
the
iterator
returned,
we
can't
actually
do
that
because
the
thing
that
was
actually
returned
has
no
reference
to
the
packet
parser
anymore
right,
so
the
the
reader
that
we
have
is
in
the
packet
parser.
But
the
item
that
we
got
back
from
the
iterator
has
no
connection
to
the
packet
parser
anymore,
so
we
can
only
return
fully
processed
packets.
A
That's
not
good!
For
streaming
operations.
We'd
have
to
read
everything
into
memory.
So
the
alternative
is
that
we
go
ahead
and
pass
the
reader
in
the
iterator
with,
for
instance,
a
reference.
Well,
it
turns
out.
You
can't
do
that
due
to
lifetimes,
because
you
could
have
multiple
items
that
are
alive
at
the
same
time
and
we
definitely
require
a
mutable
reference
right
because
we're
reading
from
a
file.
A
So
we
have
to
do
some
sort
of
adjustments
on
the
cursor,
so
the
iterator
API
just
doesn't
provide
what
we
want
and
the
other
problem
is
that
we
kind
of
want
to
preserve
the
structure
of
the
open,
PGP
message
right.
It
looks
like
a
tree
and
using
the
iterator
it
effectively
flattens
the
whole
tree
structure.
So
that's
a
little
bit
sad
that
we
lost
out
information.
There
are
ways
to
recover
it,
but
it's
just
a
side
observation.
So
how
can
we
create
an
iterator
like
API
that
still
supports
all
of
our
requirements?
A
Well,
this
is
what
we
came
up
with.
In
the
end,
we
use
a
while
let
and
we
have
a
type
called
PPR
or
a
packet
parser
result,
and
the
packet
parser
result
contains
the
packet
that
we're
currently
parsing,
so
we're
still
able
to
do
streaming
operations
on
the
current
packet
and
then,
when
we're
done
doing
the
streaming
operations,
we
get
the
next
object
and
at
the
same
time,
the
packet
parser
returns.
The
item
that
we're
currently
working
on,
and
now
we
own
the
item.
A
So
we
can
go
ahead
and
squirrel
it
away
and
a
vector
if
we
want,
which
you
sometimes
want
to
do
when
you
have,
for
instance,
the
PKS
Kay,
what
we
can
just
throw
it
away
so
they're
kind
of
three
phases.
The
first
phase
is
where
we
do
the
streaming,
then
we
have
the
next
phase,
and
then
we
have
the
phase
where
we
actually
own
the
packet,
and
here
we're
using
PP
recurse
to
get
the
next
packet
and
so
you're
at
a
container
boundary.
A
A
A
Sometimes
you
want
to
collect
some
statistics,
for
instance,
so
you
want
to
do
some
processing
or
you
somehow
need
to
return
a
result
and
it's
inconvenient
to
kind
of
return
it
via
the
callbacks
result
and
see
you
do
this
using
a
cookie.
So
here
we
have
a
callback
state
at
the
top
with
cues
of
structure.
Then
we
have
our
actual
callback.
So
the
thing
that
our
function
is
going
to
call
and
it
takes
the
cookie-
then
we
convert
this
void
pointer
into
our
actual
cookies
type.
We
can
do
whatever
type
of
processing
we
want.
A
We
save
our
information
in
there
and
then,
when
the
function
returns,
our
state
here
at
the
bottom
is
filled
with
whatever
the
callback
insert
it
into
the
cookie.
Now,
that's
the
idiomatic
way
to
do
that
and
see
button
rust,
that's
hard
to
do,
because
you
wouldn't
need
a
mutable
reference
and
all
of
a
sudden,
you
get
some
sort
of
crazy
errors
from
rusts
bar
or
checker,
and
it
doesn't
work
out
so
nicely.
So
what
can
we
do?
Instead,
well
in
rust,
you
don't
need
to
use
a
cookie.
A
You
can
use
a
trait
because
a
trait
can
transparently
encapsulate
whatever
type
of
state
you
want.
So
here
we
have
a
so-called
callback
helper,
which
has
a
function
signature
in
it
called
callback,
and
it
has
no
cookie
in
it
right.
This
is
the
thing
that
the
function
is
going
to
call,
and
then
we
implement
this
trait
for
our
own
type.
So
for
our
cookie.
So
here
we
have
a
struck
callback
and
we
can
insert
whatever
fields
we
want
in
there
and
then,
when
our
function
down
here
at
the
bottom
wants
to
invoke
the
callback.
A
A
You
can
use
this
or
do
this
using
failure.
Compat.
So
here
I
have
an
example
of
how
you
can
do
this.
We
do
a
little
bit
of
down
casting
and
either
we
have
an
I/o
error,
or
maybe
we
have
a
failure,
and
then
we
can
do
some
sort
of
conversion.
It's
all
a
little
bit
magic
and
then
you
can
recover
it
from
here.
We
have
IO
copy,
we
got
our
result.
We
do
a
map
error,
we
can
recover
our
failure
or
if
we
have
a
plain
IO
error,
we
get
to
play
an
IO
error
back.
A
Verifier
is
our
custom
reader
that
supports
the
failure,
interface
and,
of
course,
I'll
copy
doesn't,
but
still
we're
able
to
somehow
get
it
back.
We've
opened
up
a
an
issue
and
provided
a
at
least
partial
solution
for
the
failure
crate,
and
we
hope
that
without
boats
we'll
we'll
take
a
look
at
it
eventually.
A
So
using
this
pattern,
you're
still
able
to
use
these
rich
errors
that
you
have
or
work
with
them,
even
though
the
current
API
doesn't
quite
support
them.
So
those
are
the
matrix
challenges
that
we
faced
and
maybe
I've
kind
of
piqued
your
interest
in
Sequoia.
So
I'm,
going
to
tell
you
a
little
bit
about
it.
Sequoia
is
low-level.
Api
is
about
98%
feature
complete.
It
includes
a
CFO
fie
as
part
of
developing
Sequoia.
A
We
ported
a
number
of
applications
to
Sequoia,
including
the
pep
engine,
which
is
where
I
work,
and
one
thing
we
found
is
even
though
we've
only
implemented
a
low-level
API.
So
far,
it
only
requires
about
a
60%
only
requires
60%
as
much
code
as
the
version
of
pep
using
GPG.
So
we're
particularly
pleased
about
that.
We
also
have
a
QT
TV
replacement,
which
is
the
tool
that,
for
instance,
apt
uses
to
verify
packages.
We
have
a
new
key
server
implementation
that
Kai
has
worked
on
and
we've
done
experiments
porting
other
software.
A
A
A
And
if
you're
interested
you're
welcome
to
join
us
and
in
fact
we're
hiring
people
so
we're
looking
for
people
that
have
Android
and
iOS
experience,
not
just
to
do
ports
to
Android
and
iOS,
but
to
help
with
a
more
tighter
integration
in
the
future.
So
Sequoia
is
this
new
awesome.
We
think
open,
PGP
implementation.
Our
focus
is
on
user
centric
development
and
a
strong
focus
on
security.
A
We
try
to
be
really
portable
and
highly
integrated
with
the
environment,
so
not
just
thinking
about
how
things
ought
to
be
done,
but
using
the
services
that
the
host
OS
provides
so,
for
instance,
on
iOS.
We
want
to
use
the
trusted
uncle'
if
we
want
to
use
systemd
on
on
linux
when
it's
there,
our
low-level
API
is
already
very
usable,
and
if
you
have
a
package
within
a
couple
of
minutes,
you'll
be
able
to
add
a
dependency
to
sequoia,
open
PGP
and
integrate
Sequoia
into
your
project.
We
are
on
freenode
in
the
Sequoia
channel.
C
Yes,
so
I
was
wondering
if
you're
trying
to
fill
in
a
void
also
that
is
currently
with
GPG
at
least
last
time,
I
checked
for
having
an
secure
and
easy
to
use
API
to
interface
with
this
sort
of
functionality
from
a
code
directly
as
far
as
I
know,
GPG
only
encourages
like
wrapped
command-line
use,
but
mostly
and
not
like
direct
interfacing
with
this
stuff,
saticon
I'm.
Sorry.
A
At
all,
so
there
is
a
library
that
qpq
provides
called,
keep
eg
me
and
it's
a
C
library
and
they're
also
wrappers
for
other
languages.
Now,
in
the
background,
it
is
indeed
calling
out
to
the
GP
key
command
line
tool,
and
this
isn't
necessarily
a
bad
thing,
because
you
should
kind
of
be
happy
that
you
have
process
separation,
so
you
might
have
heard
of
heartbleed,
which
is
in
open,
SSL
bug,
and
one
of
the
reasons
that
hot
lead
occurred
was
because
the
private
keys
were
held
in
the
same
memory
as
the
process.
A
So
there
was
an
error
in
whatever
daemon
you
happen
to
have,
and
it
was
able
to
arbitrary
reads.
It
reads
out
the
private
key
and
now
you're
screwed.
If
you
have
this
process
separation,
it's
not
ironclad
right.
No
security
provides
protection
from
everything,
but
it
does
provide
you
protection
from
those
types
of
errors.
So
doing
this
type
of
separation
is
sensible.
One
of
the
issues
that
we
have
with
this
approach
is
that
keep
Akemi
offers
a
subset
of
the
API
that
GPC
supports,
and
one
consequence
of
this
is
that
people
start
using
GPT
me.
A
They
think
all
this
is
a
nice
library
maybe,
and
they
encounter-
is
something
that
they
can't
do
with
qpg
me,
but
they
can
do
on
the
command
line
and
say:
okay.
Well,
then
I'll
just
shell
out
to
keep
EG,
because
it
can
do
it
on
the
command
line
and
they
write
all
of
this
infrastructure
to
shell
out
parse
the
output
and
then
they
think
well
wait.
Why
am
I
using
CPC
me
when
I
already
have
this
infrastructure
to
shell
out
and
so
we're
taking
the
library
first
approach
and
command
line?
A
A
A
My
main
key
is
up
here
so
in
terms
of
talking
to
GPG
agent
from
Sequoia.
We
haven't
implemented
that
support,
but
if
you
think
about
GPG
agent
as
being
similar
to
a
smart
card,
then
it's
not
difficult
to
imagine
that
once
we
have
smart
card
support,
interfacing
with
the
TPP
should
be
or
keeping
keys.
Agent
should
be
relatively
easy.
One
of
the
difficulties
there
is
that
GPG
G's
agent
uses
a
sort
of
proprietary
way
of
generating
the
fingerprint.
A
A
So
it's
we
took
a
look
at
it
as
a
way
of
getting
early
support
for
doing
private
key
operations
and
decided
that
to
be
actually
just
easier
to
implement
the
crypto
ourselves
or
not
actually
the
crypto
stuff,
but
the
using
the
low-level
primitives.
And
so
that's
what
we
did.
But
for
practical
reasons
it
does
make
sense
to
consider
interfacing
with
the
Qt
agents,
and
there
is
an
issue
in
our
issue
tracker
and
maybe
we'll
do
that.
Actually,
we'll
probably
do
that.