aboutsummaryrefslogtreecommitdiff
path: root/archive/2022-03-15-notes-on-sigsum-and-rekor.md
blob: d19e69618b68e34d924719e1aacfdb7084240ec7 (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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
# A frequently asked question
We are frequently asked how Sigsum compares to Sigstore.  This document
highlights some technical similarities and differences as of March, 2022.

Sigstore's sub-project Rekor and Sigsum are both transparency log designs that
aim to make a signer's key-usage transparent.  By enabling end-users to verify
that signatures they trust are public, they are protected from accepting
malicious signatures that were created in secret (e.g., due to key compromise).

The following transparency log concepts are explored side-by-side:

  - Purpose of logging
  - What is logged
  - Auditing
  - Gossip
  - Anti-poison
  - Anti-spam
  - Privacy
  - API
  - Promises of future logging (SCTs)
  - Sharding

There is also an Appendix at the end with relevant code snippets.

## Purpose of logging

**Sigsum**

No signature that an end-user accepts as valid should go unnoticed by anyone who
inspects the log.

Sigsum is designed to be secure even if an attacker controls:

  - The signer's infrastructure and signing key
  - The log's infrastructure and signing key
  - A threshold of independent witnesses that cosign the log

The log operator is not trusted beyond being available at the time of logging.

**Sigstore/Rekor**

[Rekor's README][] says that the goal is to "[e]nable software maintainers and
build systems to record signed metadata to an immutable record", and that it
"fulfils the signature transparency role of sigstore's software signing
infrastructure".  [No threat model][] is available at the time of writing.

[Rekor's README]: https://github.com/sigstore/rekor/tree/6ace9fe63b072a3a7e8b544fcbf393d2aafe9ae5#readme
[No threat model]: https://web.archive.org/web/20220312143825/https://docs.sigstore.dev/security/

## What is logged

**Sigsum**

A shard hint, a SHA256 checksum, an Ed25519 signature, and a SHA256-hashed
public key.  The signed blob is based on the [SSH format][].  The logged
signatures are verifiable by anyone who knows the corresponding public keys.

[SSH format]: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig

**Sigstore/Rekor**

Signed manifests called [pluggable types][].  The rekord type allows signatures
based on Signify, Minisign, SSH, and PGP, as well as PKIX/X.509.  For example,
you could log a signature for a PGP email that was received in 2002.  There are
several other pluggable types with more to come.  Rekor is overall permissive.

Some pluggable type signatures are not verifiable in isolation for a monitor
that observes the log because to verify a signature data is needed that is not
included in the log and might be known only to the creator of the signature, see
"Example 1: unverifiable signatures" in the Appendix at the end.  This means
that it is impossible to distinguish between unverifiable log entries fabricated
by the log and log entries maliciously created with a signer's private key.

[pluggable types]: https://github.com/sigstore/rekor/blob/6ace9fe63b072a3a7e8b544fcbf393d2aafe9ae5/pkg/types/README.md

## Auditing
How will end-users verify that public logging took place?

**Sigsum**

End-users receive inclusion proofs and cosigned tree heads that can be verified
offline in the same way that data and signatures are currently delivered (a
website, a git repository, etc).  No other network communication is required.

This means that a sigsum log does not have to be available to the end-user after
logging succeeded to verify that public logging took place.

**Sigstore/Rekor**

End-users [query the log][] to check that a signature is included.  This means
the log needs to stay available to end-users at all times, also see "Privacy".

Alternatively, an end-user may trust a signed promise (SCT) from Rekor to
include a signature in the future, see "Promises of future log inclusion".

[query the log]: https://web.archive.org/web/20220312144545/https://www.sigstore.dev/how-it-works

## Gossip
A mechanism that ensures everyone observes the same append-only log.  A
transparency log without gossip requires trusting the log operator and the
integrity of the log's signing key.

**Sigsum**

Gossip is handled proactively using a simplified version of witness cosigning
([S&P 2016][]).  It can be thought of as a policy-based consensus mechanism:
m-of-n witnesses audit the log's consistency from their respective vantage
points.  The log distributes the witnesses' signatures, which end-users and
monitors make use of.  This ensures that a malicious sigsum log operator is
unable to fool even an offline verifier.

[S&P 2016]: https://arxiv.org/pdf/1503.08768.pdf

**Sigstore/Rekor**

Does not have gossip.  Sigstore proposes to instead use multiple independent
[full-fledged logs][].  Logs have more freedom to fool a verifier than cosigning
witnesses and are harder and more expensive to run.

Signers would have to take great care to submit the same entries to all the logs
and would have to monitor all of them.  End-users would have to contact and/or
trust multiple logs when verifying.

[full-fledged logs]: https://web.archive.org/web/20220312144801/http://web.archive.org/screenshot/https://docs.sigstore.dev/faq

## Anti-poison
A transparency log design must take caution to not include large amounts of
arbitrary data, as someone could submit information that is illegal to possess
or distribute. Because the log operator cannot selectively erase information
from the log without invalidating it, the operator may be compelled to shut down
the log, or simply choose to not operate a log in the first place.

**Sigsum**

Include as little arbitrary information as possible.  A log entry contains a
time value (a shard hint, in the interval `[shard_start, now()]`) of which the
least significant few bytes are arbitrary. The remaining content can only be
specified by brute forcing signatures or hashes so that parts of them contain
certain patterns.  This quickly becomes expensive.

**Sigstore/Rekor**

The pluggable type system allows logging of a wide variety of information.  The
rekord type for instance allows encoding an image, see "Example 2: logging JPG
images" in the Appendix at the end.  Other current and future pluggable types
might permit similar things but we have not looked into that further.

## Anti-spam
An approach to limit abusive logging requests that consume the log's capacity.

**Sigsum**

A logging request is only accepted if the involved public key is hashed into a
DNS TXT record.  So, rate limits can be based on DNS names.  This is similar to
the anti-spam mechanisms that CT relies on, where a certificate is not accepted
unless it chains up to a trusted CA.  Let's Encrypt [rate-limits via DNS][].

[rate-limits via DNS]: https://web.archive.org/web/20220312145502/https://letsencrypt.org/docs/rate-limits/

**Sigstore/Rekor**

Recently opened an issue about [needing rate-limits][].  It has since been added
to Sigstore's infrastructure setup based on IP addresses via Nginx.

[needing rate-limits]: https://github.com/sigstore/rekor/issues/637

## Privacy

**Sigsum**

For end-users, nothing changes with regard to privacy compared to signature
verification that does not make use of a transparency log.

A signer has to expose to the log operator a public key and a 32-byte preimage
([which should be a hash][]).

The signer's hashed public key is exposed in DNS, see "Anti spam".  Logs can be
be instructed to fetch such TXT resource records if they match `_sigsum_v0.*`.

Log entries consisting of a shard hint, hashes and signatures become public, see
"What is logged".

[which should be a hash]: https://git.sigsum.org/sigsum/tree/doc/design.md?id=741a65ab1894b35c9cc132d9b8401776c04fe1ce#n351

**Sigstore/Rekor**

End-users that query the log for entries they are interested in expose this
information to the log.

A signer's public key and data is exposed to the log operator.  The data may be
a hash if [using X.509/PKIX][] signatures and the "hashed rekord type".

Logs can be instructed to download arbitrary data from [specified URLs][].

The content of pluggable types become public, notably including public keys and
other data that may be encoded (which depends on the exact pluggable type).

[using X.509/PKIX]: https://github.com/sigstore/rekor/blob/6ace9fe63b072a3a7e8b544fcbf393d2aafe9ae5/types.md#hashed-rekord
[specified URLs]: https://github.com/sigstore/rekor/blob/6ace9fe63b072a3a7e8b544fcbf393d2aafe9ae5/openapi.yaml#L143

## API

**Sigsum**

Simple ASCII parsers are used to add input and get output from the log.  Binary
data is hex-encoded.  Data that is signed and/or logged can be (de)serialized
using Tor's easy wire-format, [Trunnel][].  The overall take of the API is to
keep it as simple as possible, and to nudge towards correct usage by design.

[Trunnel]: https://gitweb.torproject.org/trunnel.git/tree/doc/trunnel.md

**Sigstore/Rekor**

REST API with JSON, both for input and output as well as canonicalization of
data.  Some parts of the API are easily misused due to returning redundant
and/or unauthenticated data.  This happened [once][] and [twice][] already.

Other types of mis-usage are likely to be expected.  For example, the
availability of public keys makes it tempting to inappropriately use Rekor for
key discovery even though it is not designed to be a trustworthy source of keys.

The implementation complexity of Rekor's API is also relatively large.  Imports
contain much hidden complexity, in part due to pluggable types that involve PGP,
ASN.1, and such.  This has already lead to issues like Rekor starting to return
[YAML instead of JSON][]; JSON marshalling of tree leaves [not being canonical
enough][]; the [need of another PGP library][]; etc.  It will likely be hard to
understand, implement, and maintain Rekor compliant software in the long run.

[once]: https://github.com/sigstore/rekor/issues/200
[twice]: https://github.com/sigstore/rekor/pull/469
[YAML instead of JSON]: https://github.com/sigstore/rekor/issues/593
[not being canonical enough]: https://github.com/sigstore/rekor/pull/445
[need of another PGP library]: https://github.com/sigstore/rekor/issues/286

## Promises of future log inclusion (SCTs)
A construct that enables low-latency logging.  Certificate Transparency
introduced this to work with the existing TLS PKI during a gradual roll-out
scenario.  Nine years later such roll-outs are still in the earlier stages.

**Sigsum**

Does not support SCTs. Unlike the TLS case there is no existing system to be
compatible with and use-cases that cannot tolerate a few minutes of logging
latency are out-of-scope.  This is a trade-off that keeps the design simple.

**Sigstore/Rekor**

[Supports SCTs][]. If an entry (promised to be logged by an SCT) is not included
in the log in time the operator should not be trusted in the future.  This
places high requirements on uptime and availability, and on not losing the log
request between the time of issuing an SCT and inclusion in the log.  Rekor
avoids this by simply including the signature in the log [before issuing][] the
SCT. That however provides no latency reduction, making SCTs pointless.

The mere existence of SCTs also encourages their use, making users who rely on
them not receive the full benefits of using a transparency log.  We are not
aware of any clear plans specifying how Sigstore will solve SCT verification.
Without such plans, the log may deceive end-users by changing its history.

[Supports SCTs]: https://twitter.com/lorenc_dan/status/1388109774579982340
[before issuing]: https://github.com/sigstore/rekor/blob/a61d5f63843cbae4e5bf1f97d06628fa914a4435/openapi.yaml#L493

## Sharding
The practise of dividing a log into smaller independent partitions.  One of
Certificate Transparency's successes is [temporal sharding][] based on expiry
date to allow log rotation (helpful to make log life cycles more manageable).

[temporal sharding]: https://web.archive.org/web/20220312150931/https://googlechrome.github.io/CertificateTransparency/log_policy.html

**Sigsum**

Establishes shards based on shard hints in the interval `[shard_start, now()]`.
Shard hints are part of the signer's signing context and cannot be forged,
preventing past entries from being re-logged in a newer shard.  A log can also
cease its operations safely due to Sigsum's take on offline auditing.  This
gives a complete story for log life cycles without any expiration dates.

**Sigstore/Rekor**

Creates [virtual shards][], see also related [GitHub issues][].  The latest
virtual shard is active.  All other virtual shards are kept around but in
read-only mode.  If a virtual shard was to be deleted those entries can be
logged again.  As old virtual shards have to be saved, this gives a complicated
log configuration but does not provide any of the benefits of sharding.

[virtual shards]: https://docs.google.com/document/d/1QBTyK-wquplNdeUB5_aqztQHigJOepCvd-4FL4H-zl8/edit?resourcekey=0-grdVbSltkTvpNvhj03laCQ#
[GitHub issues]: https://github.com/sigstore/rekor/issues/353

# Appendix
## Example 1: Unverifiable signatures
The input to a signature verification is a public key, a message, and a
signature.  Rekor incorrectly assumes that the signed message ("artifact")
is always a hash.

First we demonstrate the issue using `signify`.

**Step 1 - Generate a key-pair**

    $ signify-openbsd -G -c "demo" -p example.pub -s example.sec
    passphrase:
    confirm passphrase:
    $ ls
    example.pub  example.sec

**Step 2 - Create something to sign**

    $ echo 'print("hello")' > hello.py
    $ sha256sum hello.py
    b80792336156c7b0f7fe02eeef24610d2d52a10d1810397744471d1dc5738180  hello.py
    $ ls
    example.pub  example.sec  hello.py

**Step 3 - Sign**

    $ signify-openbsd -Ss example.sec -m hello.py
    passphrase:
    $ ls
    example.pub  example.sec  hello.py  hello.py.sig

**Step 4 - Log**

The below uses Rekor on commit 6ace9fe63b072a3a7e8b544fcbf393d2aafe9ae5.

    $ rekor-cli upload \
          --artifact hello.py\
          --signature hello.py.sig\
          --pki-format=minisign\
          --public-key=example.pub\
          --rekor_server http://localhost:3000
    Created entry at index 99, available at: http://localhost:3000/api/v1/log/entries/bf5f87c83bfffc3b0d3d0151a6cfef836594dcd90ac286541b25856b7d6fd6b1
    $ rekor-cli get --log-index 99 --rekor_server http://localhost:3000
    LogID: e0f8a8ff472431bda298489c292f33f5d30363949df58d2e35c5195f915c7069
    Index: 99
    IntegratedTime: 2022-03-12T11:59:28Z
    UUID: bf5f87c83bfffc3b0d3d0151a6cfef836594dcd90ac286541b25856b7d6fd6b1
    Body: {
      "RekordObj": {
        "data": {
          "hash": {
            "algorithm": "sha256",
            "value": "b80792336156c7b0f7fe02eeef24610d2d52a10d1810397744471d1dc5738180"
          }
        },
        "signature": {
          "content": "dW50cnVzdGVkIGNvbW1lbnQ6ClJXVE9PWDNrMjE0L3VDR0VuQWtHaC9wOGxCK2o4ZlU0bzN2aFpzc3dud2RFeDE2M2REUHpReE1BVXdUcUFjK1ovaGdycGdISU5VNHlFbnhPOXNwSnkydmZmcHdWSUJIV3lnWT0=",
          "format": "minisign",
          "publicKey": {
            "content": "UldUT09YM2syMTQvdUg1RVhHbUFwdDZWSnhxVUlJZzIrWnMrajlFRDNTM1ZGOXBHSzZoWDRLSmM="
          }
        }
      }
    }

Let's understand the above in more detail.  Our public key and signature is in
the above output, but without the untrusted comment which is irrelevant.

    cat example.pub
    untrusted comment: demo public key
    RWTOOX3k214/uH5EXGmApt6VJxqUIIg2+Zs+j9ED3S3VF9pGK6hX4KJc
    $ echo UldUT09YM2syMTQvdUg1RVhHbUFwdDZWSnhxVUlJZzIrWnMrajlFRDNTM1ZGOXBHSzZoWDRLSmM= | base64 -d
    RWTOOX3k214/uH5EXGmApt6VJxqUIIg2+Zs+j9ED3S3VF9pGK6hX4KJc
    $
    $ cat hello.py.sig
    untrusted comment: verify with example.pub
    RWTOOX3k214/uCGEnAkGh/p8lB+j8fU4o3vhZsswnwdEx163dDPzQxMAUwTqAc+Z/hgrpgHINU4yEnxO9spJy2vffpwVIBHWygY=
    $ echo "dW50cnVzdGVkIGNvbW1lbnQ6ClJXVE9PWDNrMjE0L3VDR0VuQWtHaC9wOGxCK2o4ZlU0bzN2aFpzc3dud2RFeDE2M2REUHpReE1BVXdUcUFjK1ovaGdycGdISU5VNHlFbnhPOXNwSnkydmZmcHdWSUJIV3lnWT0=" | base64 -d
    untrusted comment:
    RWTOOX3k214/uCGEnAkGh/p8lB+j8fU4o3vhZsswnwdEx163dDPzQxMAUwTqAc+Z/hgrpgHINU4yEnxO9spJy2vffpwVIBHWygY=

Rekor also stored a SHA256 hash: 

    "RekordObj": {
      "data": {
        "hash": {
          "algorithm": "sha256",
          "value": "b80792336156c7b0f7fe02eeef24610d2d52a10d1810397744471d1dc5738180"
        }
      },

You might recognize it from step 2.  It is the output of `sha256sum hello.py`.
What is needed to do verification with the logged signature and public key is
the output of `cat hello.py`.  This is because signify signs those exact bytes
without doing any hashing (unlike other schemes that actually do sign a hash).

The impact is that you cannot know by observing the log if a real signature
operation happened (there is nothing to verify), or if the Rekor operator
fabricated an entry.  The signature would be verifiable if the data can be
located.  In the case of a malicious signature this might not be the case.

**The same issue in other places**

Minisign has the same issue, even with its pre-hashed mode because Minisign
[does not use SHA256][] which is what Rekor still stores.  The exact same issue
is also there for SSH signatures.  We did not dig into any other types.

[does not use SHA256]: https://github.com/sigstore/rekor/blob/6ace9fe63b072a3a7e8b544fcbf393d2aafe9ae5/pkg/pki/minisign/minisign.go#L109

## Example 2: Logging JPG images
As shown above Rekor scrubs some information that was submitted for logging.
Most likely to not accept arbitrary bytes, such as signify's untrusted comments.
To protect Rekor against logging of illegal content, each pluggable type needs
to be considered in more detail.  Below is an example that encodes a JPG image.

**Step 1 - Create an image**

    $ convert -size 16x16 xc:red red.jpg
    $ ls
    red.jpg

This also works with larger images, e.g., `-size 4096x4096`.

**Step 2 - Encode image in an X.509 configuration**

    $ echo "[ req ]" > crt.cnf
    $ echo "distinguished_name = dn" >> crt.cnf
    $ echo "x509_extensions = extensions" >> crt.cnf
    $ echo "prompt = no" >> crt.cnf
    $ echo "" >> crt.cnf
    $ echo "[ extensions ]" >> crt.cnf
    $ echo "1.2.3.4 = ASN1:UTF8String:$(base64 -w0 red.jpg)" >> crt.cnf
    $ echo "" >> crt.cnf
    $ echo "[ dn ]" >> crt.cnf
    $ echo "0.DC = com" >> crt.cnf
    $ echo "1.DC = example" >> crt.cnf
    $ echo "commonName = example.com" >> crt.cnf
    $ ls
    crt.cnf  red.jpg

Credit: [this post][] outlines how an X.509v3 extension can be prepared.

[this post]: https://serverfault.com/questions/1005029/error-when-trying-to-add-custom-extensions-to-x509-certificates-using-openssl

**Step 3 - Create X.509 certificate**

    $ openssl req -x509 -nodes\
            -newkey ec:<(openssl ecparam -name prime256v1)\
            -config crt.cnf\
            -keyout priv.pem\
            -out cert.pem
    Generating an EC private key
    writing new private key to 'priv.pem'
    -----
    $ ls
    cert.pem  crt.cnf  priv.pem  red.jpg

Note that we now have an X.509 certificate that encodes our image:

    $ openssl x509 -in cert.pem -text -noout
    Certificate:
        Data:
            Version: 3 (0x2)
            Serial Number:
                27:a5:0e:37:9c:3e:e1:0d:d7:a4:8f:10:ce:2d:09:35:40:9c:a8:1e
            Signature Algorithm: ecdsa-with-SHA256
            Issuer: DC = com, DC = example, CN = example.com
            Validity
                Not Before: Mar 12 13:46:06 2022 GMT
                Not After : Apr 11 13:46:06 2022 GMT
            Subject: DC = com, DC = example, CN = example.com
            Subject Public Key Info:
                Public Key Algorithm: id-ecPublicKey
                    Public-Key: (256 bit)
                    pub:
                        04:9d:2a:5d:4c:df:d4:fa:9a:76:32:59:96:3b:44:
                        12:00:03:3c:c0:d9:42:58:c1:fb:2a:ed:fb:0d:95:
                        d0:ce:7d:62:e6:f8:ae:be:76:6b:3b:0c:44:aa:ca:
                        43:57:cf:19:a3:c9:b1:cd:05:21:a2:b8:0c:50:13:
                        0c:d9:9f:8f:ea
                    ASN1 OID: prime256v1
                    NIST CURVE: P-256
            X509v3 extensions:
                1.2.3.4:
                    ..../9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAQABADAREAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAj/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFgEBAQEAAAAAAAAAAAAAAAAAAAcJ/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AnRDGqYAAD//Z
        Signature Algorithm: ecdsa-with-SHA256
             30:46:02:21:00:ee:ee:81:d6:1c:e8:a7:ca:dd:54:b5:82:fe:
             22:1d:94:1d:b1:31:91:d6:3e:68:99:f5:d5:da:c9:f4:bc:53:
             53:02:21:00:cd:08:98:c5:73:e8:a1:8f:8c:95:06:cc:5c:70:
             65:aa:dd:94:f0:38:59:ec:f7:c7:35:98:eb:75:5f:23:eb:c5

Compare the X.509v3 extensions to this:

    $ base64 red.jpg
    /9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAQABADAREAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAj/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFgEBAQEAAAAAAAAAAAAAAAAAAAcJ/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AnRDGqYAAD//Z

Now we need to get this X.509 certificate into Rekor.

**Step 4 - Sign something**

    $ openssl dgst -sha256 -sign priv.pem -out red.jpg.sig red.jpg
    $ ls
    cert.pem  crt.cnf  priv.pem  red.jpg  red.jpg.sig

**Step 5 - Log**

    $ rekor-cli upload \
            --artifact red.jpg\
            --signature red.jpg.sig\
            --pki-format=x509\
            --public-key=cert.pem\
            --rekor_server http://localhost:3000
    Created entry at index 102, available at: http://localhost:3000/api/v1/log/entries/688f817bb1e7cb1b28eadf173b0095724cce86ab0116b0df5a0c8e06c00e880c
    $
    $ rekor-cli get --log-index 102 --rekor_server http://localhost:3000
    LogID: e0f8a8ff472431bda298489c292f33f5d30363949df58d2e35c5195f915c7069
    Index: 102
    IntegratedTime: 2022-03-12T13:51:30Z
    UUID: 688f817bb1e7cb1b28eadf173b0095724cce86ab0116b0df5a0c8e06c00e880c
    Body: {
      "RekordObj": {
        "data": {
          "hash": {
            "algorithm": "sha256",
            "value": "4a837db6d6a21a9bdfccff6ca21f72d1d4a671b59a5df76158629ff5f6b22ddf"
          }
        },
        "signature": {
          "content": "MEYCIQD4HWtXfSDGpWUUmk1tWUP/V5MRUPgwrUMiiJB7miKZcQIhAJDYUO+lyGYKShCtt3AZZilZkoLEm9WAaXhlzGVwZMu6",
          "format": "x509",
          "publicKey": {
            "content": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKakNDQXN1Z0F3SUJBZ0lVSjZVT041dys0UTNYcEk4UXppMEpOVUNjcUI0d0NnWUlLb1pJemowRUF3SXcKUkRFVE1CRUdDZ21TSm9tVDhpeGtBUmtXQTJOdmJURVhNQlVHQ2dtU0pvbVQ4aXhrQVJrV0IyVjRZVzF3YkdVeApGREFTQmdOVkJBTU1DMlY0WVcxd2JHVXVZMjl0TUI0WERUSXlNRE14TWpFek5EWXdObG9YRFRJeU1EUXhNVEV6Ck5EWXdObG93UkRFVE1CRUdDZ21TSm9tVDhpeGtBUmtXQTJOdmJURVhNQlVHQ2dtU0pvbVQ4aXhrQVJrV0IyVjQKWVcxd2JHVXhGREFTQmdOVkJBTU1DMlY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowRApBUWNEUWdBRW5TcGRUTi9VK3BwMk1sbVdPMFFTQUFNOHdObENXTUg3S3UzN0RaWFF6bjFpNXZpdXZuWnJPd3hFCnFzcERWODhabzhteHpRVWhvcmdNVUJNTTJaK1A2cU9DQVprd2dnR1ZNSUlCa1FZREtnTUVCSUlCaUF5Q0FZUXYKT1dvdk5FRkJVVk5yV2twU1owRkNRVkZCUVVGUlFVSkJRVVF2TW5kQ1JFRkJUVU5CWjBsRFFXZE5RMEZuU1VSQgpkMDFFUWtGWlJVSkJVVVZDUVdkSFFtZFZSME5SWjB0RFoydEpRMUZyUzBSQk9FMURaM05QUTNkclNrUlNSVTVFClp6aFJSVUpGVVVObmQxTkZlRWxSUlhjNFVVVkNSQzh5ZDBKRVFWRk5SRUYzVVVSQ1FXZEZRa0ZuVVVOM2EweEYKUWtGUlJVSkJVVVZDUVZGRlFrRlJSVUpCVVVWQ1FWRkZRa0ZSUlVKQlVVVkNRVkZGUWtGUlJVSkJVVVZDUVZGRgpRa0ZSUlVKQlVVVkNRVkZGUWtGUlJVSkVMM2RCUVZKRFFVRlJRVUpCUkVGU1JVRkJhRVZDUVhoRlFpODRVVUZHClVVRkNRVkZCUVVGQlFVRkJRVUZCUVVGQlFVRkJRVUZCUVdvdmVFRkJWVVZCUlVGQlFVRkJRVUZCUVVGQlFVRkIKUVVGQlFVRkJRUzg0VVVGR1owVkNRVkZGUVVGQlFVRkJRVUZCUVVGQlFVRkJRVUZCUVdOS0x6aFJRVVpDUlVKQgpRVUZCUVVGQlFVRkJRVUZCUVVGQlFVRkJRVUZRTDJGQlFYZEVRVkZCUTBWUlRWSkJSRGhCYmxKRVIzRlpRVUZFCkx5OWFNQW9HQ0NxR1NNNDlCQU1DQTBrQU1FWUNJUUR1N29IV0hPaW55dDFVdFlMK0loMlVIYkV4a2RZK2FKbjEKMWRySjlMeFRVd0loQU0wSW1NVno2S0dQakpVR3pGeHdaYXJkbFBBNFdlejN4eldZNjNWZkkrdkYKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
          }
        }
      }
    }

Of interest here is the public key:

    $ echo "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKakNDQXN1Z0F3SUJBZ0lVSjZVT041dys0UTNYcEk4UXppMEpOVUNjcUI0d0NnWUlLb1pJemowRUF3SXcKUkRFVE1CRUdDZ21TSm9tVDhpeGtBUmtXQTJOdmJURVhNQlVHQ2dtU0pvbVQ4aXhrQVJrV0IyVjRZVzF3YkdVeApGREFTQmdOVkJBTU1DMlY0WVcxd2JHVXVZMjl0TUI0WERUSXlNRE14TWpFek5EWXdObG9YRFRJeU1EUXhNVEV6Ck5EWXdObG93UkRFVE1CRUdDZ21TSm9tVDhpeGtBUmtXQTJOdmJURVhNQlVHQ2dtU0pvbVQ4aXhrQVJrV0IyVjQKWVcxd2JHVXhGREFTQmdOVkJBTU1DMlY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowRApBUWNEUWdBRW5TcGRUTi9VK3BwMk1sbVdPMFFTQUFNOHdObENXTUg3S3UzN0RaWFF6bjFpNXZpdXZuWnJPd3hFCnFzcERWODhabzhteHpRVWhvcmdNVUJNTTJaK1A2cU9DQVprd2dnR1ZNSUlCa1FZREtnTUVCSUlCaUF5Q0FZUXYKT1dvdk5FRkJVVk5yV2twU1owRkNRVkZCUVVGUlFVSkJRVVF2TW5kQ1JFRkJUVU5CWjBsRFFXZE5RMEZuU1VSQgpkMDFFUWtGWlJVSkJVVVZDUVdkSFFtZFZSME5SWjB0RFoydEpRMUZyUzBSQk9FMURaM05QUTNkclNrUlNSVTVFClp6aFJSVUpGVVVObmQxTkZlRWxSUlhjNFVVVkNSQzh5ZDBKRVFWRk5SRUYzVVVSQ1FXZEZRa0ZuVVVOM2EweEYKUWtGUlJVSkJVVVZDUVZGRlFrRlJSVUpCVVVWQ1FWRkZRa0ZSUlVKQlVVVkNRVkZGUWtGUlJVSkJVVVZDUVZGRgpRa0ZSUlVKQlVVVkNRVkZGUWtGUlJVSkVMM2RCUVZKRFFVRlJRVUpCUkVGU1JVRkJhRVZDUVhoRlFpODRVVUZHClVVRkNRVkZCUVVGQlFVRkJRVUZCUVVGQlFVRkJRVUZCUVdvdmVFRkJWVVZCUlVGQlFVRkJRVUZCUVVGQlFVRkIKUVVGQlFVRkJRUzg0VVVGR1owVkNRVkZGUVVGQlFVRkJRVUZCUVVGQlFVRkJRVUZCUVdOS0x6aFJRVVpDUlVKQgpRVUZCUVVGQlFVRkJRVUZCUVVGQlFVRkJRVUZRTDJGQlFYZEVRVkZCUTBWUlRWSkJSRGhCYmxKRVIzRlpRVUZFCkx5OWFNQW9HQ0NxR1NNNDlCQU1DQTBrQU1FWUNJUUR1N29IV0hPaW55dDFVdFlMK0loMlVIYkV4a2RZK2FKbjEKMWRySjlMeFRVd0loQU0wSW1NVno2S0dQakpVR3pGeHdaYXJkbFBBNFdlejN4eldZNjNWZkkrdkYKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" | base64 -d > logged-cert.pem
    $ diff cert.pem logged-cert.pem
    $

As you can see, it is the same certificate that contains our JPG image.  Any
other data could be encoded as well using the above trick.