Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support minisign signature creation #6

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions minisign.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
package minisign

import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"strings"
"time"

"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/ed25519"
)

type PrivateKey struct {
SignatureAlgorithm [2]byte
KDFAlgorithm [2]byte
KDFRounds [4]byte
Salt [16]byte
Checksum [8]byte
KeyId [8]byte
PrivateKey [64]byte
}

type PublicKey struct {
SignatureAlgorithm [2]byte
KeyId [8]byte
Expand Down Expand Up @@ -131,3 +144,86 @@ func (publicKey *PublicKey) VerifyFromFile(file string, signature Signature) (bo
}
return publicKey.Verify(bin, signature)
}

func DecodePrivateKey(in string) (PrivateKey, error) {
lines := strings.SplitN(in, "\n", 2)
if len(lines) < 2 {
return PrivateKey{}, errors.New("Incomplete encoded private key")
}
return NewPrivateKey(lines[1])
}

func NewPrivateKey(in string) (PrivateKey, error) {
var privateKey PrivateKey
keydata, err := base64.StdEncoding.DecodeString(in)
if err != nil {
return privateKey, err
}

if len(keydata) != 104 {
return privateKey, errors.New("Invalid encoded secret key")
}

if string(keydata[:2]) != "Ed" {
return privateKey, errors.New("Unsupported signature scheme")
}

copy(privateKey.SignatureAlgorithm[:], keydata[:2])
copy(privateKey.KDFAlgorithm[:], keydata[2:4])
copy(privateKey.KDFRounds[:], keydata[4:8])
copy(privateKey.Salt[:], keydata[8:24])
copy(privateKey.Checksum[:], keydata[24:32])
copy(privateKey.KeyId[:], keydata[32:40])
copy(privateKey.PrivateKey[:], keydata[40:])
return privateKey, nil
}

func NewPrivateKeyFromFile(file string) (PrivateKey, error) {
var privateKey PrivateKey
bin, err := ioutil.ReadFile(file)
if err != nil {
return privateKey, err
}
return DecodePrivateKey(string(bin))
}

func commentIsMoreThanOneLine(comment string) bool {
firstLFIndex := strings.IndexByte(comment, 10)
return (firstLFIndex >= 0 && firstLFIndex < len(comment)-1)
}

func (privateKey *PrivateKey) Sign(bin []byte, unTrustedComment string, trustedComment string) ([]byte, error) {
out := bytes.NewBuffer(nil)

rawSig := ed25519.Sign(privateKey.PrivateKey[:], bin)

var sigdata []byte
sigdata = append(sigdata, privateKey.SignatureAlgorithm[:]...)
sigdata = append(sigdata, privateKey.KeyId[:]...)
sigdata = append(sigdata, rawSig...)

if unTrustedComment == "" {
return nil, errors.New("Missing untrusted comment")
}
// Check that the trusted comment fits in one line
if commentIsMoreThanOneLine(unTrustedComment) {
return nil, errors.New("Untrusted comment must fit on a single line")
}
out.WriteString(fmt.Sprintf("untrusted comment: %s\n%s\n", unTrustedComment, base64.StdEncoding.EncodeToString(sigdata)))

// Add the trusted comment if unavailable
if trustedComment == "" {
trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix())
}
// Check that the trusted comment fits in one line
if commentIsMoreThanOneLine(trustedComment) {
return nil, errors.New("Trusted comment must fit on a single line")
}

var sigAndComment []byte
sigAndComment = append(sigAndComment, rawSig...)
sigAndComment = append(sigAndComment, []byte(trustedComment)...)
out.WriteString(fmt.Sprintf("trusted comment: %s\n%s\n", trustedComment, base64.StdEncoding.EncodeToString(ed25519.Sign(privateKey.PrivateKey[:], sigAndComment))))

return out.Bytes(), nil
}
34 changes: 34 additions & 0 deletions minisign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,37 @@ func TestPrehashed(t *testing.T) {
t.Fatal("signature verification failed")
}
}

var (
testPKey = "untrusted comment: minisign public key\nRWTAPRW2qy9FjsBiMFGCEFv9Jk3iPhAh7tZb+VOFmtmBxDyHrFT8kZuT"
testSKey = "untrusted comment: minisign secret key\nRWRCSwAAAABVN5lr2JViGBN8DhX3/Qb/0g0wBdsNAR/APRW2qy9Fjsfr12sK2cd3URUFis1jgzQzaoayK8x4syT4G3Gvlt9RwGIwUYIQW/0mTeI+ECHu1lv5U4Wa2YHEPIesVPyRm5M="
)

func TestRoundTrip(t *testing.T) {
pkey, err := DecodePublicKey(testPKey)
if err != nil {
t.Fatalf("error decoding the public key: %v", err)
}
skey, err := DecodePrivateKey(testSKey)
if err != nil {
t.Fatalf("error decoding the private key: %v", err)
}

sig, err := skey.Sign([]byte("hello"), "verify with minisign", "")
if err != nil {
t.Fatalf("error signing: %v", err)
}

signature, err := DecodeSignature(string(sig))
if err != nil {
t.Fatalf("error when decoding signature: %v", err)
}

ok, err := pkey.Verify([]byte("hello"), signature)
if err != nil {
t.Fatalf("error verifying signature: %v", err)
}
if !ok {
t.Fatal("signature could not be verified")
}
}