Skip to content

Commit

Permalink
🌱 Initial commit of Pinki as a standalone binary
Browse files Browse the repository at this point in the history
  • Loading branch information
canterberry committed Jan 7, 2022
0 parents commit 7f3532d
Show file tree
Hide file tree
Showing 16 changed files with 493 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
github:
- canterberry
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
golang 1.17.6
19 changes: 19 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright 2022 Twuni

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
133 changes: 133 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Pinki

Pinki helps developers ship software with authenticity.

Use it anywhere you would use `gpg` to sign and verify things.

## Features

* Easy to use
* Portable, standalone binary
* Anonymous -- a key is just a key, nothing more
* Doesn't touch your filesystem
* Reads and writes standard PEM-wrapped ASN.1 (compatible with X.509, GPG)

## Installing

### Precompiled Binaries

Visit [Releases](https://releases.twuni.dev/pinki/latest/) to download a precompiled binary for your system.

#### Verifying Binaries

All releases are signed using the following [release signing key](https://releases.twuni.dev/verify.pem):

```pem
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEdDOWMNxI5f88Yck8WNcPsxDOwMbzoU/Y
cZhfoR+gwGi0wRoSscWA1xy1BQTG6PNrQlvLJbfm2vAIAImnyMmhoKS3hwcO6F+5
4QjLZQJAQHZ6G7c842gYRSnwLLQ2GIvj
-----END PUBLIC KEY-----
```

Each release binary has a corresponding file with an **.asc** prefix containing the signature for that file.

Here's an example of how you could use Pinki to verify itself.

> :bulb: In practice, you probably want to use another tool to verify Pinki itself the first time you download
> it. Once you have a genuine copy of `pinki`, then you can use it to verify updates to itself.
```sh
# Download Pinki for Linux (64-bit)
$ curl -sSL -o pinki https://releases.twuni.dev/pinki/latest/linux-amd64/pinki

# Use Pinki to verify itself
$ ./pinki verify "$(curl -sSL https://releases.twuni.dev/verify.pem)" "$(curl -sSL https://releases.twuni.dev/pinki/latest/linux-amd64/pinki.asc)" < pinki
```

If you get an output of `OK`, the signature is valid.

### Building from source

Already have `go`? Clone this repo and run `go build`.

## Usage

Pinki is designed to make it easy for you to do one of two things:

* **Sign** your software so other people can verify its authenticity, or

* **Verify** the authenticity of software you are using when the developers
are using Pinki.

### Signing your software with Pinki

First, you'll need a private key. To create a new key with the
recommended (default) options:

```sh
$ pinki key create
-----BEGIN PRIVATE KEY-----
...............................................................
...............................................................
...............................................................
-----END PRIVATE KEY-----
```

Save the output somewhere safe. Put it in your password manager,
vault, or whatever you are using to keep sensitive information
safe.

Once you have a private key, you will need to *export* that in a
way that is safe for people to verify your signatures:

```sh
$ pinki key export < /path/to/your-pinki-private-key
-----BEGIN PUBLIC KEY-----
...............................................................
...............................................................
-----END PUBLIC KEY-----
```

Publish this public key somewhere that anyone you want to be able
to verify your signatures is able to access it. You can commit it
to your source code repo, publish it to your website, etc.

> :bulb: The **public key** is not sensitive! You can safely share
> it with anyone.
Now that you have a private key, you're ready to sign your first thing!

```sh
$ pinki sign "$(cat /path/to/your-pinki-private-key)" < /path/to/your-thing-1.2.3.tar.gz
-----BEGIN SIGNATURE-----
...............................................................
...............................................................
-----END SIGNATURE-----
```

Publish that signature any way you like. Conventionally, you might want to
publish it as a file with the same name as the thing you've signed, but with
a **.sig** suffix. So **foo-1.0.tgz** would have its signature in
**foo-1.0.tgz.sig**. The choice is up to you.

### Verifying a signature with Pinki

To verify a signature, you'll need three things:

* The thing that was signed (e.g: **foo-1.2.3.tgz**)
* The signature (e.g: **foo-1.2.3.tgz.asc**)
* The public key of the signer (e.g: **foomaker-signing-key.pem**)

Check the release notes or installation/verification documentation of the
thing you're trying to verify for more details on where to find these things.

Once you have them, here's how you verify the thing is authentic!

```sh
$ pinki verify "$(cat /path/to/signing-key)" "$(cat /path/to/signature)" < /path/to/thing-that-was-signed
OK
```

The command will exit with status code 0 and print "OK" on success.
Otherwise, it will exit with status code 1 and print an error message.
43 changes: 43 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"io"
)

func cli(args []string, in io.Reader, out io.Writer) error {
if len(args) < 1 {
return help(out)
}

switch args[0] {
case "help":
return help(out)
case "key":
if len(args) < 2 {
return help(out)
}

switch args[1] {
case "create":
return createPrivateKey(out)
case "export":
return exportPublicKey(in, out)
default:
return help(out)
}
case "sign":
if len(args) < 2 {
return help(out)
}

return sign(args[1], in, out)
case "verify":
if len(args) < 3 {
return help(out)
}

return verify(args[1], args[2], in, out)
}

return help(out)
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/twuni/pinki

go 1.17
28 changes: 28 additions & 0 deletions help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"errors"
"io"
)

func help(out io.Writer) error {
return errors.New(`USAGE: pinki <command>
EXAMPLES
$ pinki help
Display this help message.
$ pinki key create > private.pem
Generate a new private key and write the result to "private.pem".
$ pinki key export < private.pem > public.pem
Extract the public key from "private.pem" and write the result to "public.pem".
$ pinki sign "$(cat private.pem)" < package-1.0.0.tgz > package-1.0.0.tgz.asc
Sign "package-1.0.0.tgz" using the private key from "private.pem" and write the result to "package-1.0.0.tgz.asc".
$ pinki verify "$(cat public.pem)" "$(cat package-1.0.0.tgz.asc)" < package-1.0.0.tgz
Verify the signature in "package-1.0.0.tgz.asc" of "package-1.0.0.tgz" using the public key from "public.pem".
`)
}
31 changes: 31 additions & 0 deletions key_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"io"
)

func createPrivateKey(out io.Writer) error {
key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)

if err != nil {
return err
}

der, err := x509.MarshalPKCS8PrivateKey(key)

if err != nil {
return err
}

block := &pem.Block{
Type: "PRIVATE KEY",
Bytes: der,
}

return pem.Encode(out, block)
}
28 changes: 28 additions & 0 deletions key_export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"crypto/x509"
"encoding/pem"
"io"
)

func exportPublicKey(in io.Reader, out io.Writer) error {
key, err := readPrivateKey(in)

if err != nil {
return err
}

der, err := x509.MarshalPKIXPublicKey(key.Public())

if err != nil {
return err
}

block := &pem.Block{
Type: "PUBLIC KEY",
Bytes: der,
}

return pem.Encode(out, block)
}
10 changes: 10 additions & 0 deletions key_help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

import (
"io"
)

func keyHelp(out io.Writer) error {
out.Write([]byte("USAGE: pinki key create|export|help\n"))
return nil
}
17 changes: 17 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"fmt"
"os"
)

func main() {
err := cli(os.Args[1:], os.Stdin, os.Stdout)

if err != nil {
fmt.Printf("%s\n", err)
os.Exit(1)
}

os.Exit(0)
}
35 changes: 35 additions & 0 deletions read_private_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
"io"
)

const (
PrivateKey = "PRIVATE KEY"
)

func readPrivateKey(in io.Reader) (*ecdsa.PrivateKey, error) {
buffer, err := io.ReadAll(in)

if err != nil {
return nil, err
}

block, _ := pem.Decode(buffer)

if block == nil || block.Type != PrivateKey {
return nil, errors.New("Expected a PEM-encoded private key")
}

key, err := x509.ParsePKCS8PrivateKey(block.Bytes)

if err != nil {
return nil, err
}

return key.(*ecdsa.PrivateKey), nil
}
35 changes: 35 additions & 0 deletions read_public_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
"io"
)

const (
PublicKey = "PUBLIC KEY"
)

func readPublicKey(in io.Reader) (*ecdsa.PublicKey, error) {
buffer, err := io.ReadAll(in)

if err != nil {
return nil, err
}

block, _ := pem.Decode(buffer)

if block == nil || block.Type != PublicKey {
return nil, errors.New("Expected a PEM-encoded public key")
}

key, err := x509.ParsePKIXPublicKey(block.Bytes)

if err != nil {
return nil, err
}

return key.(*ecdsa.PublicKey), nil
}
Loading

0 comments on commit 7f3532d

Please sign in to comment.