mirror of
https://github.com/gsmith257-cyber/better-sliver
synced 2026-06-06 15:44:30 +00:00
Move minisign to util/
This commit is contained in:
@@ -36,7 +36,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
)
|
||||
|
||||
// ArmoryIndex - Index JSON containing alias/extension/bundle information
|
||||
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
)
|
||||
|
||||
// ErrPackageNotFound - The package was not found
|
||||
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
)
|
||||
|
||||
// ArmoryIndexParser - Generic interface to fetch armory indexes
|
||||
|
||||
+8
-8
@@ -67,6 +67,14 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# util / minisign
|
||||
if go test -tags=server,$TAGS ./util/minisign ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Implant
|
||||
|
||||
# implant / sliver / extension
|
||||
@@ -131,14 +139,6 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / cryptography / minisign
|
||||
if go test -tags=server,$TAGS ./server/cryptography/minisign ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / gogo
|
||||
if go test -tags=server,$TAGS ./server/gogo ; then
|
||||
:
|
||||
|
||||
@@ -36,9 +36,9 @@ import (
|
||||
"sync"
|
||||
|
||||
"filippo.io/age"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"testing"
|
||||
|
||||
implantCrypto "github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Andreas Auernhammer
|
||||
|
||||
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.
|
||||
@@ -1 +0,0 @@
|
||||
Hello World!
|
||||
@@ -1,4 +0,0 @@
|
||||
untrusted comment: signature from minisign secret key
|
||||
RWRQhGcHOBlzwxrJCyuC+rJfHSfyRKRxkuwa3JJ0bWEs7RHjL1OUmqnTr+V1B9JzFuJIH/ybR2Eus9oEZKt9RbitpF/L4D3+5wg=
|
||||
trusted comment: timestamp:1614549543 file:message.txt
|
||||
P/722+ynQ+tIy0qadFHwLx5MsyNz/jDKJkDWQj4dDD2OKnVte8m/M14mwPE/1NMwzShPMSBhMXqZGdbe+UZjDg==
|
||||
@@ -1,2 +0,0 @@
|
||||
untrusted comment: minisign encrypted secret key
|
||||
RWRTY0Iytaz5znJmUO5kBt5xVkvpBl+29A7pZH86phD4h8vD3V8AAAACAAAAAAAAAEAAAAAA9vH9EcS6NdXNIEGhYGoqG1CiL4aptyJreJ4IfuT4+1h+OgVaY/vi0HsbCP0Y6n/wcy0AN0wOXmVDPP33jZqv82YCj2fH+/6MRuAfzNQYoLvc3sH/8bIwqdfpKIjDRZhvqRf063RFYoI=
|
||||
@@ -1,2 +0,0 @@
|
||||
untrusted comment: minisign public key C373193807678450
|
||||
RWRQhGcHOBlzw4CoKyugkk4ioDfoxlXxC9LBx+VNhJ3w9w+cAxgvPsuo
|
||||
@@ -1,4 +0,0 @@
|
||||
untrusted comment: signature from minisign secret key
|
||||
RWQ3ly9IPenQ6XE4gvV0tpJPSRdw/Si+Q4r97LbpLj0Hb3sV+XFydynJg3iFT2PjIlE3xViNOmFT9XrIoidedDr41+Ly0AYbUQg=
|
||||
trusted comment: timestamp:1617721023 file:robtest.ps1
|
||||
HkxuqHSvipJo/unNKgDS+JGDB0+Q5d8nOeoJ0NGOnKBNsNdvAj8FWf7fhaPV7mzRJ1ooLvYpI0yUsD7lpaDwBQ==
|
||||
@@ -1,267 +0,0 @@
|
||||
// Copyright (c) 2021 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
// Modifications moloch--
|
||||
|
||||
// Package minisign implements the minisign signature scheme.
|
||||
package minisign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
const (
|
||||
// EdDSA refers to the Ed25519 signature scheme.
|
||||
//
|
||||
// Minisign uses this signature scheme to sign and
|
||||
// verify (non-hashed) messages.
|
||||
EdDSA uint16 = 0x6445
|
||||
|
||||
// HashEdDSA refers to a Ed25519 signature scheme
|
||||
// with pre-hashed messages.
|
||||
//
|
||||
// Minisign uses this signature scheme to sign and
|
||||
// verify message that don't fit into memory.
|
||||
HashEdDSA uint16 = 0x4445
|
||||
|
||||
RawSigSize = 2 + 8 + ed25519.SignatureSize
|
||||
)
|
||||
|
||||
// GenerateKey generates a public/private key pair using entropy
|
||||
// from random. If random is nil, crypto/rand.Reader will be used.
|
||||
func GenerateKey(random io.Reader) (PublicKey, PrivateKey, error) {
|
||||
pub, priv, err := ed25519.GenerateKey(random)
|
||||
if err != nil {
|
||||
return PublicKey{}, PrivateKey{}, err
|
||||
}
|
||||
|
||||
id := blake2b.Sum256(pub[:])
|
||||
publicKey := PublicKey{
|
||||
id: binary.LittleEndian.Uint64(id[:8]),
|
||||
}
|
||||
copy(publicKey.bytes[:], pub)
|
||||
|
||||
privateKey := PrivateKey{
|
||||
RawID: publicKey.ID(),
|
||||
}
|
||||
copy(privateKey.RawBytes[:], priv)
|
||||
|
||||
return publicKey, privateKey, nil
|
||||
}
|
||||
|
||||
// Reader is an io.Reader that reads a message
|
||||
// while, at the same time, computes its digest.
|
||||
//
|
||||
// At any point, typically at the end of the message,
|
||||
// Reader can sign the message digest with a private
|
||||
// key or try to verify the message with a public key
|
||||
// and signature.
|
||||
type Reader struct {
|
||||
message io.Reader
|
||||
hash hash.Hash
|
||||
}
|
||||
|
||||
// NewReader returns a new Reader that reads from r
|
||||
// and computes a digest of the read data.
|
||||
func NewReader(r io.Reader) *Reader {
|
||||
h, err := blake2b.New512(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &Reader{
|
||||
message: r,
|
||||
hash: h,
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads from the underlying io.Reader as specified
|
||||
// by the io.Reader interface.
|
||||
func (r *Reader) Read(p []byte) (int, error) {
|
||||
n, err := r.message.Read(p)
|
||||
r.hash.Write(p[:n])
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Sign signs whatever has been read from the underlying
|
||||
// io.Reader up to this point in time with the given private
|
||||
// key.
|
||||
//
|
||||
// It behaves like SignWithComments but uses some generic comments.
|
||||
func (r *Reader) Sign(privateKey PrivateKey) []byte {
|
||||
var (
|
||||
trustedComment = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
)
|
||||
return r.SignWithComments(privateKey, trustedComment, "")
|
||||
}
|
||||
|
||||
// SignWithComments signs whatever has been read from the underlying
|
||||
// io.Reader up to this point in time with the given private key.
|
||||
//
|
||||
// The trustedComment as well as the untrustedComment are embedded into the
|
||||
// returned signature. The trustedComment is signed and will be checked
|
||||
// when the signature is verified. The untrustedComment is not signed and
|
||||
// must not be trusted.
|
||||
//
|
||||
// SignWithComments computes the digest as a snapshot. So, it is possible
|
||||
// to create multiple signatures of different message prefixes by reading
|
||||
// up to a certain byte, signing this message prefix, and then continue
|
||||
// reading.
|
||||
func (r *Reader) SignWithComments(privateKey PrivateKey, trustedComment, untrustedComment string) []byte {
|
||||
const isHashed = true
|
||||
return sign(privateKey, r.hash.Sum(nil), trustedComment, untrustedComment, isHashed)
|
||||
}
|
||||
|
||||
// Verify checks whether whatever has been read from the underlying
|
||||
// io.Reader up to this point in time is authentic by verifying it
|
||||
// with the given public key and signature.
|
||||
//
|
||||
// Verify computes the digest as a snapshot. Therefore, Verify can
|
||||
// verify any signature produced by Sign or SignWithComments,
|
||||
// including signatures of partial messages, given the correct
|
||||
// public key and signature.
|
||||
func (r *Reader) Verify(publicKey PublicKey, signature []byte) bool {
|
||||
const isHashed = true
|
||||
return verify(publicKey, r.hash.Sum(nil), signature, isHashed)
|
||||
}
|
||||
|
||||
// Sign signs the given message with the private key.
|
||||
//
|
||||
// It behaves like SignWithComments with some generic comments.
|
||||
func Sign(privateKey PrivateKey, message []byte) []byte {
|
||||
var (
|
||||
trustedComment = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
)
|
||||
return SignWithComments(privateKey, message, trustedComment, "")
|
||||
}
|
||||
|
||||
// SignRawBuf - Sign buffer with raw signature
|
||||
func SignRawBuf(privateKey PrivateKey, message []byte) [RawSigSize]byte {
|
||||
return signRaw(privateKey, message, false)
|
||||
}
|
||||
|
||||
// VerifyRawBuf - Verify buffer with raw signature
|
||||
func VerifyRawBuf(publicKey PublicKey, rawMessage []byte) bool {
|
||||
return verifyRaw(publicKey, rawMessage, false)
|
||||
}
|
||||
|
||||
// SignWithComments signs the given message with the private key.
|
||||
//
|
||||
// The trustedComment as well as the untrustedComment are embedded
|
||||
// into the returned signature. The trustedComment is signed and
|
||||
// will be checked when the signature is verified.
|
||||
// The untrustedComment is not signed and must not be trusted.
|
||||
func SignWithComments(privateKey PrivateKey, message []byte, trustedComment, untrustedComment string) []byte {
|
||||
const isHashed = false
|
||||
return sign(privateKey, message, trustedComment, untrustedComment, isHashed)
|
||||
}
|
||||
|
||||
// Verify checks whether message is authentic by verifying
|
||||
// it with the given public key and signature. It returns
|
||||
// true if and only if the signature verification is successful.
|
||||
func Verify(publicKey PublicKey, message, signature []byte) bool {
|
||||
const isHashed = false
|
||||
return verify(publicKey, message, signature, isHashed)
|
||||
}
|
||||
|
||||
func signRaw(privateKey PrivateKey, message []byte, isHashed bool) [RawSigSize]byte {
|
||||
var algorithm = EdDSA
|
||||
if isHashed {
|
||||
algorithm = HashEdDSA
|
||||
}
|
||||
var (
|
||||
msgSignature = ed25519.Sign(ed25519.PrivateKey(privateKey.RawBytes[:]), message)
|
||||
)
|
||||
var rawSig [RawSigSize]byte
|
||||
binary.LittleEndian.PutUint16(rawSig[:2], algorithm)
|
||||
binary.LittleEndian.PutUint64(rawSig[2:10], privateKey.ID())
|
||||
copy(rawSig[10:], msgSignature[:])
|
||||
return rawSig
|
||||
}
|
||||
|
||||
func verifyRaw(publicKey PublicKey, rawMessage []byte, isHashed bool) bool {
|
||||
if len(rawMessage) < RawSigSize+1 {
|
||||
return false
|
||||
}
|
||||
rawSigBuf := rawMessage[:RawSigSize]
|
||||
algorithm := binary.LittleEndian.Uint16(rawSigBuf[:2])
|
||||
keyID := binary.LittleEndian.Uint64(rawSigBuf[2:10])
|
||||
signature := rawSigBuf[10:]
|
||||
message := rawMessage[RawSigSize:]
|
||||
|
||||
if keyID != publicKey.ID() {
|
||||
return false
|
||||
}
|
||||
if algorithm == HashEdDSA && !isHashed {
|
||||
h := blake2b.Sum512(message)
|
||||
message = h[:]
|
||||
}
|
||||
if !ed25519.Verify(ed25519.PublicKey(publicKey.bytes[:]), message, signature[:]) {
|
||||
return false
|
||||
}
|
||||
return ed25519.Verify(ed25519.PublicKey(publicKey.bytes[:]), message, signature[:])
|
||||
}
|
||||
|
||||
func sign(privateKey PrivateKey, message []byte, trustedComment, untrustedComment string, isHashed bool) []byte {
|
||||
var algorithm = EdDSA
|
||||
if isHashed {
|
||||
algorithm = HashEdDSA
|
||||
}
|
||||
|
||||
var (
|
||||
msgSignature = ed25519.Sign(ed25519.PrivateKey(privateKey.RawBytes[:]), message)
|
||||
commentSignature = ed25519.Sign(ed25519.PrivateKey(privateKey.RawBytes[:]), append(msgSignature, []byte(trustedComment)...))
|
||||
)
|
||||
signature := Signature{
|
||||
Algorithm: algorithm,
|
||||
KeyID: privateKey.ID(),
|
||||
|
||||
TrustedComment: trustedComment,
|
||||
UntrustedComment: untrustedComment,
|
||||
}
|
||||
copy(signature.Signature[:], msgSignature)
|
||||
copy(signature.CommentSignature[:], commentSignature)
|
||||
|
||||
text, err := signature.MarshalText()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func verify(publicKey PublicKey, message, signature []byte, isHashed bool) bool {
|
||||
var s Signature
|
||||
if err := s.UnmarshalText(signature); err != nil {
|
||||
return false
|
||||
}
|
||||
if s.KeyID != publicKey.ID() {
|
||||
return false
|
||||
}
|
||||
if s.Algorithm == HashEdDSA && !isHashed {
|
||||
h := blake2b.Sum512(message)
|
||||
message = h[:]
|
||||
}
|
||||
if !ed25519.Verify(ed25519.PublicKey(publicKey.bytes[:]), message, s.Signature[:]) {
|
||||
return false
|
||||
}
|
||||
globalMessage := append(s.Signature[:], []byte(s.TrustedComment)...)
|
||||
return ed25519.Verify(ed25519.PublicKey(publicKey.bytes[:]), globalMessage, s.CommentSignature[:])
|
||||
}
|
||||
|
||||
// trimUntrustedComment returns text with a potential
|
||||
// untrusted comment line.
|
||||
func trimUntrustedComment(text []byte) []byte {
|
||||
s := bytes.SplitN(text, []byte{'\n'}, 2)
|
||||
if len(s) == 2 && strings.HasPrefix(string(s[0]), "untrusted comment: ") {
|
||||
return s[1]
|
||||
}
|
||||
return s[0]
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright (c) 2021 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package minisign
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRoundtrip(t *testing.T) {
|
||||
const Password = "correct horse battery staple"
|
||||
privateKey, err := PrivateKeyFromFile(Password, "./internal/testdata/minisign.key")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load private key: %v", err)
|
||||
}
|
||||
|
||||
message, err := os.ReadFile("./internal/testdata/message.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load message: %v", err)
|
||||
}
|
||||
signature := Sign(privateKey, message)
|
||||
|
||||
publicKey, err := PublicKeyFromFile("./internal/testdata/minisign.pub")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load public key: %v", err)
|
||||
}
|
||||
|
||||
if !Verify(publicKey, message, signature) {
|
||||
t.Fatalf("Verification failed: signature %q - public key %q", signature, publicKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReaderRoundtrip(t *testing.T) {
|
||||
const Password = "correct horse battery staple"
|
||||
privateKey, err := PrivateKeyFromFile(Password, "./internal/testdata/minisign.key")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load private key: %v", err)
|
||||
}
|
||||
|
||||
file, err := os.Open("./internal/testdata/message.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open message: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
reader := NewReader(file)
|
||||
if _, err = io.Copy(io.Discard, reader); err != nil {
|
||||
t.Fatalf("Failed to read message: %v", err)
|
||||
}
|
||||
signature := reader.Sign(privateKey)
|
||||
|
||||
publicKey, err := PublicKeyFromFile("./internal/testdata/minisign.pub")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load public key: %v", err)
|
||||
}
|
||||
if !reader.Verify(publicKey, signature) {
|
||||
t.Fatalf("Verification failed: signature %q - public key %q", signature, publicKey)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,319 +0,0 @@
|
||||
// Copyright (c) 2021 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package minisign
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/blake2b"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
// PrivateKeyFromFile reads and decrypts the private key
|
||||
// file with the given password.
|
||||
func PrivateKeyFromFile(password, path string) (PrivateKey, error) {
|
||||
bytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return PrivateKey{}, err
|
||||
}
|
||||
return DecryptKey(password, bytes)
|
||||
}
|
||||
|
||||
// PrivateKey is a minisign private key.
|
||||
//
|
||||
// A private key can sign messages to prove the
|
||||
// their origin and authenticity.
|
||||
//
|
||||
// PrivateKey implements the crypto.Signer interface.
|
||||
type PrivateKey struct {
|
||||
_ [0]func() // prevent direct comparison: p1 == p2.
|
||||
|
||||
RawID uint64
|
||||
RawBytes [ed25519.PrivateKeySize]byte
|
||||
}
|
||||
|
||||
func (p PrivateKey) Bytes() []byte {
|
||||
return p.RawBytes[:]
|
||||
}
|
||||
|
||||
var _ crypto.Signer = (*PrivateKey)(nil) // compiler check
|
||||
|
||||
// ID returns the 64 bit key ID.
|
||||
func (p PrivateKey) ID() uint64 { return p.RawID }
|
||||
|
||||
// Public returns the corresponding public key.
|
||||
func (p PrivateKey) Public() crypto.PublicKey {
|
||||
var bytes [ed25519.PublicKeySize]byte
|
||||
copy(bytes[:], p.RawBytes[32:])
|
||||
|
||||
return PublicKey{
|
||||
id: p.ID(),
|
||||
bytes: bytes,
|
||||
}
|
||||
}
|
||||
|
||||
// Sign signs the given message.
|
||||
//
|
||||
// The minisign signature scheme relies on Ed25519 and supports
|
||||
// plain as well as pre-hashed messages. Therefore, opts can be
|
||||
// either crypto.Hash(0) to signal that the message has not been
|
||||
// hashed or crypto.BLAKE2b_512 to signal that the message is a
|
||||
// BLAKE2b-512 digest. If opts is crypto.BLAKE2b_512 then message
|
||||
// must be a 64 bytes long.
|
||||
//
|
||||
// Minisign signatures are deterministic such that no randomness
|
||||
// is necessary.
|
||||
func (p PrivateKey) Sign(_ io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
var (
|
||||
trustedComment = "timestamp:" + strconv.FormatInt(time.Now().Unix(), 10)
|
||||
untrustedComment = "signature from private key: " + strings.ToUpper(strconv.FormatUint(p.ID(), 16))
|
||||
)
|
||||
switch h := opts.HashFunc(); h {
|
||||
case crypto.Hash(0):
|
||||
const isHashed = false
|
||||
return sign(p, message, trustedComment, untrustedComment, isHashed), nil
|
||||
case crypto.BLAKE2b_512:
|
||||
const isHashed = true
|
||||
if n := len(message); n != blake2b.Size {
|
||||
return nil, errors.New("minisign: invalid message length " + strconv.Itoa(n))
|
||||
}
|
||||
return sign(p, message, trustedComment, untrustedComment, isHashed), nil
|
||||
default:
|
||||
return nil, errors.New("minisign: cannot sign messages hashed with " + strconv.Itoa(int(h)))
|
||||
}
|
||||
}
|
||||
|
||||
// Equal returns true if and only if p and x have equivalent values.
|
||||
func (p PrivateKey) Equal(x crypto.PrivateKey) bool {
|
||||
xx, ok := x.(PrivateKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return p.RawID == xx.RawID && subtle.ConstantTimeCompare(p.RawBytes[:], xx.RawBytes[:]) == 1
|
||||
}
|
||||
|
||||
const (
|
||||
scryptAlgorithm = 0x6353 // hex value for "Sc"
|
||||
blake2bAlgorithm = 0x3242 // hex value for "B2"
|
||||
|
||||
scryptOpsLimit = 0x2000000 // max. Scrypt ops limit based on libsodium
|
||||
scryptMemLimit = 0x40000000 // max. Scrypt mem limit based on libsodium
|
||||
|
||||
privateKeySize = 158 // 2 + 2 + 2 + 32 + 8 + 8 + 104
|
||||
)
|
||||
|
||||
// EncryptKey encrypts the private key with the given password
|
||||
// using some entropy from the RNG of the OS.
|
||||
func EncryptKey(password string, privateKey PrivateKey) ([]byte, error) {
|
||||
var privateKeyBytes [72]byte
|
||||
binary.LittleEndian.PutUint64(privateKeyBytes[:], privateKey.ID())
|
||||
copy(privateKeyBytes[8:], privateKey.RawBytes[:])
|
||||
|
||||
var salt [32]byte
|
||||
if _, err := io.ReadFull(rand.Reader, salt[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var bytes [privateKeySize]byte
|
||||
binary.LittleEndian.PutUint16(bytes[0:], EdDSA)
|
||||
binary.LittleEndian.PutUint16(bytes[2:], scryptAlgorithm)
|
||||
binary.LittleEndian.PutUint16(bytes[4:], blake2bAlgorithm)
|
||||
|
||||
const ( // TODO(aead): Callers may want to customize the cost parameters
|
||||
defaultOps = 33554432 // libsodium OPS_LIMIT_SENSITIVE
|
||||
defaultMem = 1073741824 // libsodium MEM_LIMIT_SENSITIVE
|
||||
)
|
||||
copy(bytes[6:38], salt[:])
|
||||
binary.LittleEndian.PutUint64(bytes[38:], defaultOps)
|
||||
binary.LittleEndian.PutUint64(bytes[46:], defaultMem)
|
||||
copy(bytes[54:], encryptKey(password, salt[:], defaultOps, defaultMem, privateKeyBytes[:]))
|
||||
|
||||
const comment = "untrusted comment: minisign encrypted secret key\n"
|
||||
encodedBytes := make([]byte, len(comment)+base64.StdEncoding.EncodedLen(len(bytes)))
|
||||
copy(encodedBytes, []byte(comment))
|
||||
base64.StdEncoding.Encode(encodedBytes[len(comment):], bytes[:])
|
||||
return encodedBytes, nil
|
||||
}
|
||||
|
||||
var errDecrypt = errors.New("minisign: decryption failed")
|
||||
|
||||
// DecryptKey tries to decrypt the encrypted private key with
|
||||
// the given password.
|
||||
func DecryptKey(password string, privateKey []byte) (PrivateKey, error) {
|
||||
privateKey = trimUntrustedComment(privateKey)
|
||||
bytes := make([]byte, base64.StdEncoding.DecodedLen(len(privateKey)))
|
||||
n, err := base64.StdEncoding.Decode(bytes, privateKey)
|
||||
if err != nil {
|
||||
return PrivateKey{}, err
|
||||
}
|
||||
bytes = bytes[:n]
|
||||
|
||||
if len(bytes) != privateKeySize {
|
||||
return PrivateKey{}, errDecrypt
|
||||
}
|
||||
if a := binary.LittleEndian.Uint16(bytes[:2]); a != EdDSA {
|
||||
return PrivateKey{}, errDecrypt
|
||||
}
|
||||
if a := binary.LittleEndian.Uint16(bytes[2:4]); a != scryptAlgorithm {
|
||||
return PrivateKey{}, errDecrypt
|
||||
}
|
||||
if a := binary.LittleEndian.Uint16(bytes[4:6]); a != blake2bAlgorithm {
|
||||
return PrivateKey{}, errDecrypt
|
||||
}
|
||||
|
||||
var (
|
||||
scryptOps = binary.LittleEndian.Uint64(bytes[38:46])
|
||||
scryptMem = binary.LittleEndian.Uint64(bytes[46:54])
|
||||
)
|
||||
if scryptOps > scryptOpsLimit {
|
||||
return PrivateKey{}, errDecrypt
|
||||
}
|
||||
if scryptMem > scryptMemLimit {
|
||||
return PrivateKey{}, errDecrypt
|
||||
}
|
||||
var salt [32]byte
|
||||
copy(salt[:], bytes[6:38])
|
||||
privateKeyBytes, err := decryptKey(password, salt[:], scryptOps, scryptMem, bytes[54:])
|
||||
if err != nil {
|
||||
return PrivateKey{}, err
|
||||
}
|
||||
|
||||
key := PrivateKey{
|
||||
RawID: binary.LittleEndian.Uint64(privateKeyBytes[:8]),
|
||||
}
|
||||
copy(key.RawBytes[:], privateKeyBytes[8:])
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// encryptKey encrypts the plaintext and returns a ciphertext by:
|
||||
// 1. tag = BLAKE2b-256(EdDSA-const || plaintext)
|
||||
// 2. keystream = Scrypt(password, salt, convert(ops, mem))
|
||||
// 3. ciphertext = (plaintext || tag) ⊕ keystream
|
||||
//
|
||||
// Therefore, decryptKey converts the ops and mem cost parameters
|
||||
// to the (N, r, p)-tuple expected by Scrypt.
|
||||
//
|
||||
// The plaintext must be a private key ID concatenated with a raw
|
||||
// Ed25519 private key, and therefore, 72 bytes long.
|
||||
func encryptKey(password string, salt []byte, ops, mem uint64, plaintext []byte) []byte {
|
||||
const (
|
||||
plaintextLen = 72
|
||||
messageLen = 74
|
||||
ciphertextLen = 104
|
||||
)
|
||||
|
||||
N, r, p := convertScryptParameters(ops, mem)
|
||||
keystream, err := scrypt.Key([]byte(password), salt, N, r, p, ciphertextLen)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var message [messageLen]byte
|
||||
binary.LittleEndian.PutUint16(message[:2], EdDSA)
|
||||
copy(message[2:], plaintext)
|
||||
checksum := blake2b.Sum256(message[:])
|
||||
|
||||
var ciphertext [ciphertextLen]byte
|
||||
copy(ciphertext[:plaintextLen], plaintext)
|
||||
copy(ciphertext[plaintextLen:], checksum[:])
|
||||
|
||||
for i, k := range keystream {
|
||||
ciphertext[i] ^= k
|
||||
}
|
||||
return ciphertext[:]
|
||||
}
|
||||
|
||||
// decryptKey decrypts the ciphertext and returns a plaintext by:
|
||||
// 1. keystream = Scrypt(password, salt, convert(ops, mem))
|
||||
// 2. plaintext || tag = ciphertext ⊕ keystream
|
||||
// 3. Check that: tag == BLAKE2b-256(EdDSA-const || plaintext)
|
||||
//
|
||||
// Therefore, decryptKey converts the ops and mem cost parameters to
|
||||
// the (N, r, p)-tuple expected by Scrypt.
|
||||
//
|
||||
// It returns an error if the ciphertext is not valid - i.e. if the
|
||||
// tag does not match the BLAKE2b-256 hash value.
|
||||
func decryptKey(password string, salt []byte, ops, mem uint64, ciphertext []byte) ([]byte, error) {
|
||||
const (
|
||||
plaintextLen = 72
|
||||
messageLen = 74
|
||||
ciphertextLen = 104
|
||||
)
|
||||
if len(ciphertext) != ciphertextLen {
|
||||
return nil, errDecrypt
|
||||
}
|
||||
|
||||
N, r, p := convertScryptParameters(ops, mem)
|
||||
keystream, err := scrypt.Key([]byte(password), salt, N, r, p, ciphertextLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var plaintext [ciphertextLen]byte
|
||||
for i, k := range keystream {
|
||||
plaintext[i] = ciphertext[i] ^ k
|
||||
}
|
||||
var (
|
||||
privateKeyBytes = plaintext[:plaintextLen]
|
||||
checksum = plaintext[plaintextLen:]
|
||||
)
|
||||
|
||||
var message [messageLen]byte
|
||||
binary.LittleEndian.PutUint16(message[:2], EdDSA)
|
||||
copy(message[2:], privateKeyBytes)
|
||||
|
||||
if sum := blake2b.Sum256(message[:]); subtle.ConstantTimeCompare(sum[:], checksum[:]) != 1 {
|
||||
return nil, errDecrypt
|
||||
}
|
||||
return privateKeyBytes, nil
|
||||
}
|
||||
|
||||
// convertScryptParameters converts the operational and memory cost
|
||||
// to the Scrypt parameters N, r and p.
|
||||
//
|
||||
// N is the overall memory / CPU cost and r * p has to be lower then
|
||||
// 2³⁰. Refer to the scrypt.Key docs for more information.
|
||||
func convertScryptParameters(ops, mem uint64) (N, r, p int) {
|
||||
const (
|
||||
minOps = 1 << 15
|
||||
maxRP = 0x3fffffff
|
||||
)
|
||||
if ops < minOps {
|
||||
ops = minOps
|
||||
}
|
||||
|
||||
if ops < mem/32 {
|
||||
r, p = 8, 1
|
||||
for n := 1; n < 63; n++ {
|
||||
if N = 1 << n; uint64(N) > (ops / (8 * uint64(r))) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r = 8
|
||||
for n := 1; n < 63; n++ {
|
||||
if N = 1 << n; uint64(N) > (mem / (256 * uint64(r))) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if rp := (ops / 4) / uint64(N); rp < maxRP {
|
||||
p = int(rp) / r
|
||||
} else {
|
||||
p = maxRP / r
|
||||
}
|
||||
}
|
||||
return N, r, p
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package minisign
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PublicKeyFromFile reads a new PublicKey from the
|
||||
// given file.
|
||||
func PublicKeyFromFile(path string) (PublicKey, error) {
|
||||
bytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return PublicKey{}, err
|
||||
}
|
||||
|
||||
var key PublicKey
|
||||
if err = key.UnmarshalText(bytes); err != nil {
|
||||
return PublicKey{}, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// PublicKey is a minisign public key.
|
||||
//
|
||||
// A public key is used to verify whether messages
|
||||
// have been signed with the corresponding private
|
||||
// key.
|
||||
type PublicKey struct {
|
||||
_ [0]func() // prevent direct comparison: p1 == p2.
|
||||
|
||||
id uint64
|
||||
bytes [ed25519.PublicKeySize]byte
|
||||
}
|
||||
|
||||
// ID returns the 64 bit key ID.
|
||||
func (p PublicKey) ID() uint64 { return p.id }
|
||||
|
||||
// Equal returns true if and only if p and x have equivalent values.
|
||||
func (p PublicKey) Equal(x crypto.PublicKey) bool {
|
||||
xx, ok := x.(PublicKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return p.id == xx.id && p.bytes == xx.bytes
|
||||
}
|
||||
|
||||
// String returns a base64 string representation of the PublicKey p.
|
||||
func (p PublicKey) String() string {
|
||||
var bytes [2 + 8 + ed25519.PublicKeySize]byte
|
||||
binary.LittleEndian.PutUint16(bytes[:2], EdDSA)
|
||||
binary.LittleEndian.PutUint64(bytes[2:10], p.ID())
|
||||
copy(bytes[10:], p.bytes[:])
|
||||
|
||||
return base64.StdEncoding.EncodeToString(bytes[:])
|
||||
}
|
||||
|
||||
// MarshalText returns a textual representation of the PublicKey p.
|
||||
//
|
||||
// It never returns an error.
|
||||
func (p PublicKey) MarshalText() ([]byte, error) {
|
||||
var comment = "untrusted comment: minisign public key: " + strings.ToUpper(strconv.FormatUint(p.ID(), 16)) + "\n"
|
||||
return []byte(comment + p.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText parses text as textual-encoded public key.
|
||||
// It returns an error if text is not a well-formed public key.
|
||||
func (p *PublicKey) UnmarshalText(text []byte) error {
|
||||
text = trimUntrustedComment(text)
|
||||
bytes := make([]byte, base64.StdEncoding.DecodedLen(len(text)))
|
||||
n, err := base64.StdEncoding.Decode(bytes, text)
|
||||
if err != nil {
|
||||
return fmt.Errorf("minisign: invalid public key: %v", err)
|
||||
}
|
||||
bytes = bytes[:n] // Adjust b/c text may contain '\r' or '\n' which would have been ignored during decoding.
|
||||
|
||||
if n = len(bytes); n != 2+8+ed25519.PublicKeySize {
|
||||
return errors.New("minisign: invalid public key length " + strconv.Itoa(n))
|
||||
}
|
||||
if a := binary.LittleEndian.Uint16(bytes[:2]); a != EdDSA {
|
||||
return errors.New("minisign: invalid public key algorithm " + strconv.Itoa(int(a)))
|
||||
}
|
||||
|
||||
p.id = binary.LittleEndian.Uint64(bytes[2:10])
|
||||
copy(p.bytes[:], bytes[10:])
|
||||
return nil
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// Copyright (c) 2021 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package minisign
|
||||
|
||||
import "testing"
|
||||
|
||||
var marshalPublicKeyTests = []struct {
|
||||
PublicKey PublicKey
|
||||
Text string
|
||||
}{
|
||||
{
|
||||
PublicKey: PublicKey{
|
||||
id: 0xe7620f1842b4e81f,
|
||||
bytes: [32]byte{121, 165, 97, 231, 14, 224, 140, 211, 231, 84, 198, 62, 155, 214, 185, 195, 82, 10, 29, 66, 4, 205, 16, 77, 162, 231, 239, 118, 59, 24, 83, 183},
|
||||
},
|
||||
Text: "untrusted comment: minisign public key: E7620F1842B4E81F" + "\n" + "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3",
|
||||
},
|
||||
{
|
||||
PublicKey: PublicKey{
|
||||
id: 0x6f7add142cdc7edb,
|
||||
bytes: [32]byte{121, 165, 97, 231, 14, 224, 140, 211, 231, 84, 198, 62, 155, 214, 185, 195, 82, 10, 29, 66, 4, 205, 16, 77, 162, 231, 239, 118, 59, 24, 83, 183},
|
||||
},
|
||||
Text: "untrusted comment: minisign public key: 6F7ADD142CDC7EDB" + "\n" + "RWTbftwsFN16b3mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3",
|
||||
},
|
||||
}
|
||||
|
||||
func TestMarshalPublicKey(t *testing.T) {
|
||||
for i, test := range marshalPublicKeyTests {
|
||||
text, err := test.PublicKey.MarshalText()
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: failed to marshal public key: %v", i, err)
|
||||
}
|
||||
if string(text) != test.Text {
|
||||
t.Fatalf("Test %d: got '%s' - want '%s'", i, string(text), test.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unmarshalPublicKeyTests = []struct {
|
||||
Text string
|
||||
PublicKey PublicKey
|
||||
ShouldFail bool
|
||||
}{
|
||||
{
|
||||
Text: "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3",
|
||||
PublicKey: PublicKey{
|
||||
id: 0xe7620f1842b4e81f,
|
||||
bytes: [32]byte{121, 165, 97, 231, 14, 224, 140, 211, 231, 84, 198, 62, 155, 214, 185, 195, 82, 10, 29, 66, 4, 205, 16, 77, 162, 231, 239, 118, 59, 24, 83, 183},
|
||||
},
|
||||
},
|
||||
{
|
||||
Text: "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3\r\n\n",
|
||||
PublicKey: PublicKey{
|
||||
id: 0xe7620f1842b4e81f,
|
||||
bytes: [32]byte{121, 165, 97, 231, 14, 224, 140, 211, 231, 84, 198, 62, 155, 214, 185, 195, 82, 10, 29, 66, 4, 205, 16, 77, 162, 231, 239, 118, 59, 24, 83, 183},
|
||||
},
|
||||
},
|
||||
{ // Invalid algorithm
|
||||
Text: "RmQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3",
|
||||
ShouldFail: true,
|
||||
},
|
||||
{ // Invalid public key b/c too long
|
||||
Text: "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3bhQ=",
|
||||
ShouldFail: true,
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnmarshalPublicKey(t *testing.T) {
|
||||
for i, test := range unmarshalPublicKeyTests {
|
||||
var key PublicKey
|
||||
|
||||
err := key.UnmarshalText([]byte(test.Text))
|
||||
if err == nil && test.ShouldFail {
|
||||
t.Fatalf("Test %d: should have failed but passed", i)
|
||||
}
|
||||
if err != nil && !test.ShouldFail {
|
||||
t.Fatalf("Test %d: failed to unmarshal public key: %v", i, err)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if key.ID() != test.PublicKey.ID() {
|
||||
t.Fatalf("Test %d: key ID mismatch: got '%x' - want '%x'", i, key.ID(), test.PublicKey.ID())
|
||||
}
|
||||
if key.bytes != test.PublicKey.bytes {
|
||||
t.Fatalf("Test %d: raw public key mismatch: got '%v' - want '%v'", i, key.bytes, test.PublicKey.bytes)
|
||||
}
|
||||
if !key.Equal(test.PublicKey) {
|
||||
t.Fatalf("Test %d: public keys are not equal", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package minisign
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
insecureRand "math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRawSigValid(t *testing.T) {
|
||||
publicKey, privateKey, err := GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate key: %v", err)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
message := randomBuf()
|
||||
signature := SignRawBuf(privateKey, message)
|
||||
rawMsg := append(signature[:], message...)
|
||||
if !VerifyRawBuf(publicKey, rawMsg) {
|
||||
t.Fatalf("Verification failed: signature %q - public key %q", signature, publicKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawSigInvalidKey(t *testing.T) {
|
||||
_, privateKeyA, err := GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate key: %v", err)
|
||||
}
|
||||
publicKeyB, _, err := GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate key: %v", err)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
message := randomBuf()
|
||||
signature := SignRawBuf(privateKeyA, message)
|
||||
rawMsg := append(signature[:], message...)
|
||||
if VerifyRawBuf(publicKeyB, rawMsg) {
|
||||
t.Fatalf("Verification expected to fail, but didn't: signature %q - public key %q", signature, publicKeyB)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawSigInvalidTamper(t *testing.T) {
|
||||
publicKey, privateKey, err := GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate key: %v", err)
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
message := randomBuf()
|
||||
signature := SignRawBuf(privateKey, message)
|
||||
message[insecureRand.Intn(len(message))] ^= 0xFF
|
||||
message[insecureRand.Intn(len(message))] ^= 0xFF
|
||||
message[insecureRand.Intn(len(message))] ^= 0xFF
|
||||
message[insecureRand.Intn(len(message))] ^= 0xFF
|
||||
message[insecureRand.Intn(len(message))] ^= 0xFF
|
||||
rawMsg := append(signature[:], message...)
|
||||
if VerifyRawBuf(publicKey, rawMsg) {
|
||||
t.Fatalf("Verification expected to fail, but didn't: signature %q - public key %q", signature, publicKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func randomBuf() []byte {
|
||||
buf := make([]byte, insecureRand.Intn(4096)+1)
|
||||
rand.Read(buf)
|
||||
return buf
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
// Copyright (c) 2021 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package minisign
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SignatureFromFile reads a new Signature from the
|
||||
// given file.
|
||||
func SignatureFromFile(file string) (Signature, error) {
|
||||
bytes, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return Signature{}, err
|
||||
}
|
||||
|
||||
var signature Signature
|
||||
if err = signature.UnmarshalText(bytes); err != nil {
|
||||
return Signature{}, err
|
||||
}
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// Signature is a structured representation of a minisign
|
||||
// signature.
|
||||
//
|
||||
// A signature is generated when signing a message with
|
||||
// a private key:
|
||||
// signature = Sign(privateKey, message)
|
||||
//
|
||||
// The signature of a message can then be verified with the
|
||||
// corresponding public key:
|
||||
// if Verify(publicKey, message, signature) {
|
||||
// // => signature is valid
|
||||
// // => message has been signed with correspoding private key
|
||||
// }
|
||||
//
|
||||
type Signature struct {
|
||||
_ [0]func() // enforce named assignment and prevent direct comparison
|
||||
|
||||
// Algorithm is the signature algorithm. It is either
|
||||
// EdDSA or HashEdDSA.
|
||||
Algorithm uint16
|
||||
|
||||
// KeyID may be the 64 bit ID of the private key that was used
|
||||
// to produce this signature. It can be used to identify the
|
||||
// corresponding public key that can verify the signature.
|
||||
//
|
||||
// However, key IDs are random identifiers and not protected at all.
|
||||
// A key ID is just a hint to quickly identify a public key candidate.
|
||||
KeyID uint64
|
||||
|
||||
// TrustedComment is a comment that has been signed and is
|
||||
// verified during signature verification.
|
||||
TrustedComment string
|
||||
|
||||
// UntrustedComment is a comment that has not been signed
|
||||
// and is not verified during signature verification.
|
||||
//
|
||||
// It must not be considered authentic - in contrast to the
|
||||
// TrustedComment.
|
||||
UntrustedComment string
|
||||
|
||||
// Signature is the Ed25519 signature of the message that
|
||||
// has been signed.
|
||||
Signature [ed25519.SignatureSize]byte
|
||||
|
||||
// CommentSignature is the Ed25519 signature of Signature
|
||||
// concatenated with the TrustedComment:
|
||||
//
|
||||
// CommentSignature = ed25519.Sign(PrivateKey, Signature || TrustedComment)
|
||||
//
|
||||
// It is used to verify that the TrustedComment is authentic.
|
||||
CommentSignature [ed25519.SignatureSize]byte
|
||||
}
|
||||
|
||||
// String returns a string representation of the Signature s.
|
||||
//
|
||||
// In contrast to MarshalText, String does not fail if s is
|
||||
// not a valid minisign signature.
|
||||
func (s Signature) String() string {
|
||||
var buffer strings.Builder
|
||||
buffer.WriteString("untrusted comment: ")
|
||||
buffer.WriteString(s.UntrustedComment)
|
||||
buffer.WriteByte('\n')
|
||||
|
||||
var signature [2 + 8 + ed25519.SignatureSize]byte
|
||||
binary.LittleEndian.PutUint16(signature[:2], s.Algorithm)
|
||||
binary.LittleEndian.PutUint64(signature[2:10], s.KeyID)
|
||||
copy(signature[10:], s.Signature[:])
|
||||
|
||||
buffer.WriteString(base64.StdEncoding.EncodeToString(signature[:]))
|
||||
buffer.WriteByte('\n')
|
||||
|
||||
buffer.WriteString("trusted comment: ")
|
||||
buffer.WriteString(s.TrustedComment)
|
||||
buffer.WriteByte('\n')
|
||||
|
||||
buffer.WriteString(base64.StdEncoding.EncodeToString(s.CommentSignature[:]))
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Equal reports whether s and x have equivalent values.
|
||||
//
|
||||
// The untrusted comments of two equivalent signatures may differ.
|
||||
func (s Signature) Equal(x Signature) bool {
|
||||
return s.Algorithm == x.Algorithm &&
|
||||
s.KeyID == x.KeyID &&
|
||||
s.Signature == x.Signature &&
|
||||
s.CommentSignature == x.CommentSignature &&
|
||||
s.TrustedComment == x.TrustedComment
|
||||
}
|
||||
|
||||
// MarshalText returns a textual representation of the Signature s.
|
||||
//
|
||||
// It returns an error if s cannot be a valid signature - e.g.
|
||||
// because the signature algorithm is neither EdDSA nor HashEdDSA.
|
||||
func (s Signature) MarshalText() ([]byte, error) {
|
||||
if s.Algorithm != EdDSA && s.Algorithm != HashEdDSA {
|
||||
return nil, errors.New("minisign: invalid signature algorithm " + strconv.Itoa(int(s.Algorithm)))
|
||||
}
|
||||
return []byte(s.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText parses text as textual-encoded signature.
|
||||
// It returns an error if text is not a well-formed minisign
|
||||
// signature.
|
||||
func (s *Signature) UnmarshalText(text []byte) error {
|
||||
segments := strings.SplitN(string(text), "\n", 4)
|
||||
if len(segments) != 4 {
|
||||
return errors.New("minisign: invalid signature")
|
||||
}
|
||||
|
||||
var (
|
||||
untrustedComment = strings.TrimRight(segments[0], "\r")
|
||||
encodedSignature = segments[1]
|
||||
trustedComment = strings.TrimRight(segments[2], "\r")
|
||||
encodedCommentSignature = segments[3]
|
||||
)
|
||||
if !strings.HasPrefix(untrustedComment, "untrusted comment: ") {
|
||||
return errors.New("minisign: invalid signature: invalid untrusted comment")
|
||||
}
|
||||
if !strings.HasPrefix(trustedComment, "trusted comment: ") {
|
||||
return errors.New("minisign: invalid signature: invalid trusted comment")
|
||||
}
|
||||
|
||||
rawSignature, err := base64.StdEncoding.DecodeString(encodedSignature)
|
||||
if err != nil {
|
||||
return fmt.Errorf("minisign: invalid signature: %v", err)
|
||||
}
|
||||
if n := len(rawSignature); n != 2+8+ed25519.SignatureSize {
|
||||
return errors.New("minisign: invalid signature length " + strconv.Itoa(n))
|
||||
}
|
||||
commentSignature, err := base64.StdEncoding.DecodeString(encodedCommentSignature)
|
||||
if err != nil {
|
||||
return fmt.Errorf("minisign: invalid signature: %v", err)
|
||||
}
|
||||
if n := len(commentSignature); n != ed25519.SignatureSize {
|
||||
return errors.New("minisign: invalid comment signature length " + strconv.Itoa(n))
|
||||
}
|
||||
|
||||
var (
|
||||
algorithm = binary.LittleEndian.Uint16(rawSignature[:2])
|
||||
keyID = binary.LittleEndian.Uint64(rawSignature[2:10])
|
||||
)
|
||||
if algorithm != EdDSA && algorithm != HashEdDSA {
|
||||
return errors.New("minisign: invalid signature: invalid algorithm " + strconv.Itoa(int(algorithm)))
|
||||
}
|
||||
|
||||
s.Algorithm = algorithm
|
||||
s.KeyID = keyID
|
||||
s.TrustedComment = strings.TrimPrefix(trustedComment, "trusted comment: ")
|
||||
s.UntrustedComment = strings.TrimPrefix(untrustedComment, "untrusted comment: ")
|
||||
copy(s.Signature[:], rawSignature[10:])
|
||||
copy(s.CommentSignature[:], commentSignature)
|
||||
return nil
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
// Copyright (c) 2021 Andreas Auernhammer. All rights reserved.
|
||||
// Use of this source code is governed by a license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package minisign
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEqualSignature(t *testing.T) {
|
||||
for i, test := range equalSignatureTests {
|
||||
equal := test.A.Equal(test.B)
|
||||
if equal != test.Equal {
|
||||
t.Fatalf("Test %d: got 'equal=%v' - want 'equal=%v", i, equal, test.Equal)
|
||||
}
|
||||
if revEqual := test.B.Equal(test.A); equal != revEqual {
|
||||
t.Fatalf("Test %d: A == B is %v but B == A is %v", i, equal, revEqual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalInvalidSignature(t *testing.T) {
|
||||
var signature Signature
|
||||
if _, err := signature.MarshalText(); err == nil {
|
||||
t.Fatal("Marshaling invalid signature succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalSignatureRoundtrip(t *testing.T) {
|
||||
for i, test := range marshalSignatureTests {
|
||||
text, err := test.Signature.MarshalText()
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: failed to marshal signature: %v", i, err)
|
||||
}
|
||||
|
||||
var signature Signature
|
||||
if err = signature.UnmarshalText(text); err != nil {
|
||||
t.Fatalf("Test %d: failed to unmarshal signature: %v", i, err)
|
||||
}
|
||||
|
||||
if !signature.Equal(test.Signature) {
|
||||
t.Fatalf("Test %d: signature mismatch: got '%v' - want '%v'", i, signature, test.Signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalSignature(t *testing.T) {
|
||||
for i, test := range unmarshalSignatureTests {
|
||||
var signature Signature
|
||||
err := signature.UnmarshalText([]byte(test.Text))
|
||||
if err == nil && test.ShouldFail {
|
||||
t.Fatalf("Test %d: should have failed but passed", i)
|
||||
}
|
||||
if err != nil && !test.ShouldFail {
|
||||
t.Fatalf("Test %d: failed to unmarshal signature: %v", i, err)
|
||||
}
|
||||
if err == nil {
|
||||
if !signature.Equal(test.Signature) {
|
||||
t.Fatalf("Test %d: signatures are not equal: got '%s' - want '%s'", i, signature, test.Signature)
|
||||
}
|
||||
if signature.UntrustedComment != test.Signature.UntrustedComment {
|
||||
t.Fatalf("Test %d: untrusted comment mismatch: got '%s' - want '%s'", i, signature.UntrustedComment, test.Signature.UntrustedComment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignatureCarriageReturn(t *testing.T) {
|
||||
signature, err := SignatureFromFile("./internal/testdata/robtest.ps1.minisig")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read signature from file: %v", err)
|
||||
}
|
||||
if strings.HasSuffix(signature.UntrustedComment, "\r") {
|
||||
t.Fatal("Untrusted comment ends with a carriage return")
|
||||
}
|
||||
if strings.HasSuffix(signature.TrustedComment, "\r") {
|
||||
t.Fatal("Trusted comment ends with a carriage return")
|
||||
}
|
||||
}
|
||||
|
||||
var equalSignatureTests = []struct {
|
||||
A, B Signature
|
||||
Equal bool
|
||||
}{
|
||||
{
|
||||
A: Signature{}, B: Signature{}, Equal: true,
|
||||
},
|
||||
{
|
||||
A: Signature{
|
||||
Algorithm: EdDSA,
|
||||
KeyID: 0xe7620f1842b4e81f,
|
||||
UntrustedComment: `signature from minisign secret key`,
|
||||
TrustedComment: `timestamp:1591521248 file:minisign-0.9.tar.gz`,
|
||||
Signature: [64]byte{20, 99, 118, 100, 132, 21, 202, 44, 47, 123, 240, 66, 228, 28, 175, 132, 143, 49, 11, 188, 252, 49, 53, 73, 106, 154, 66, 249, 67, 203, 35, 77, 156, 24, 226, 182, 244, 241, 252, 5, 244, 97, 127, 41, 191, 156, 128, 14, 117, 64, 157, 164, 36, 146, 238, 203, 151, 33, 174, 82, 239, 66, 73, 10},
|
||||
CommentSignature: [64]byte{148, 178, 205, 92, 217, 151, 10, 78, 112, 147, 154, 17, 47, 24, 233, 136, 141, 16, 37, 217, 29, 77, 64, 75, 217, 55, 69, 178, 114, 188, 40, 93, 6, 130, 93, 121, 211, 7, 19, 198, 190, 160, 33, 49, 136, 129, 80, 249, 121, 170, 165, 216, 105, 97, 230, 151, 208, 109, 244, 227, 46, 121, 241, 15},
|
||||
},
|
||||
B: Signature{
|
||||
Algorithm: EdDSA,
|
||||
KeyID: 0xe7620f1842b4e81f,
|
||||
UntrustedComment: `signature from minisign secret key`,
|
||||
TrustedComment: `timestamp:1591521248 file:minisign-0.9.tar.gz`,
|
||||
Signature: [64]byte{20, 99, 118, 100, 132, 21, 202, 44, 47, 123, 240, 66, 228, 28, 175, 132, 143, 49, 11, 188, 252, 49, 53, 73, 106, 154, 66, 249, 67, 203, 35, 77, 156, 24, 226, 182, 244, 241, 252, 5, 244, 97, 127, 41, 191, 156, 128, 14, 117, 64, 157, 164, 36, 146, 238, 203, 151, 33, 174, 82, 239, 66, 73, 10},
|
||||
CommentSignature: [64]byte{148, 178, 205, 92, 217, 151, 10, 78, 112, 147, 154, 17, 47, 24, 233, 136, 141, 16, 37, 217, 29, 77, 64, 75, 217, 55, 69, 178, 114, 188, 40, 93, 6, 130, 93, 121, 211, 7, 19, 198, 190, 160, 33, 49, 136, 129, 80, 249, 121, 170, 165, 216, 105, 97, 230, 151, 208, 109, 244, 227, 46, 121, 241, 15},
|
||||
},
|
||||
Equal: true,
|
||||
},
|
||||
{
|
||||
A: Signature{UntrustedComment: "signature A"},
|
||||
B: Signature{UntrustedComment: "signature B"},
|
||||
Equal: true,
|
||||
},
|
||||
|
||||
{
|
||||
A: Signature{Algorithm: EdDSA},
|
||||
B: Signature{Algorithm: HashEdDSA},
|
||||
Equal: false, // Algorithm differs
|
||||
},
|
||||
{
|
||||
A: Signature{KeyID: 0xe7620f1842b4e81f},
|
||||
B: Signature{KeyID: 0x1fe8b442180f62e7},
|
||||
Equal: false, // KeyID differs
|
||||
},
|
||||
{
|
||||
A: Signature{TrustedComment: `timestamp:1591521248 file:minisign-0.9.tar.gz`},
|
||||
B: Signature{TrustedComment: `timestamp:1591521249 file:minisign-0.9.tar.gz`},
|
||||
Equal: false, // TrustedComment differs
|
||||
},
|
||||
{
|
||||
A: Signature{Signature: [64]byte{20, 99, 118, 100, 132, 21, 202, 44, 47, 123, 240, 66, 228, 28, 175, 132, 143, 49, 11, 188, 252, 49, 53, 73, 106, 154, 66, 249, 67, 203, 35, 77, 156, 24, 226, 182, 244, 241, 252, 5, 244, 97, 127, 41, 191, 156, 128, 14, 117, 64, 157, 164, 36, 146, 238, 203, 151, 33, 174, 82, 239, 66, 73, 10}},
|
||||
B: Signature{Signature: [64]byte{148, 178, 205, 92, 217, 151, 10, 78, 112, 147, 154, 17, 47, 24, 233, 136, 141, 16, 37, 217, 29, 77, 64, 75, 217, 55, 69, 178, 114, 188, 40, 93, 6, 130, 93, 121, 211, 7, 19, 198, 190, 160, 33, 49, 136, 129, 80, 249, 121, 170, 165, 216, 105, 97, 230, 151, 208, 109, 244, 227, 46, 121, 241, 15}},
|
||||
Equal: false, // Signature differs
|
||||
},
|
||||
{
|
||||
A: Signature{CommentSignature: [64]byte{20, 99, 118, 100, 132, 21, 202, 44, 47, 123, 240, 66, 228, 28, 175, 132, 143, 49, 11, 188, 252, 49, 53, 73, 106, 154, 66, 249, 67, 203, 35, 77, 156, 24, 226, 182, 244, 241, 252, 5, 244, 97, 127, 41, 191, 156, 128, 14, 117, 64, 157, 164, 36, 146, 238, 203, 151, 33, 174, 82, 239, 66, 73, 10}},
|
||||
B: Signature{CommentSignature: [64]byte{148, 178, 205, 92, 217, 151, 10, 78, 112, 147, 154, 17, 47, 24, 233, 136, 141, 16, 37, 217, 29, 77, 64, 75, 217, 55, 69, 178, 114, 188, 40, 93, 6, 130, 93, 121, 211, 7, 19, 198, 190, 160, 33, 49, 136, 129, 80, 249, 121, 170, 165, 216, 105, 97, 230, 151, 208, 109, 244, 227, 46, 121, 241, 15}},
|
||||
Equal: false, // CommentSignature differs
|
||||
},
|
||||
}
|
||||
|
||||
var marshalSignatureTests = []struct {
|
||||
Signature Signature
|
||||
}{
|
||||
{
|
||||
Signature: Signature{
|
||||
Algorithm: EdDSA,
|
||||
},
|
||||
},
|
||||
{
|
||||
Signature: Signature{
|
||||
Algorithm: EdDSA,
|
||||
KeyID: 0xe7620f1842b4e81f,
|
||||
},
|
||||
},
|
||||
{
|
||||
Signature: Signature{
|
||||
Algorithm: EdDSA,
|
||||
KeyID: 0xe7620f1842b4e81f,
|
||||
UntrustedComment: `signature from minisign secret key`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Signature: Signature{
|
||||
Algorithm: EdDSA,
|
||||
KeyID: 0xe7620f1842b4e81f,
|
||||
UntrustedComment: `signature from minisign secret key`,
|
||||
TrustedComment: `timestamp:1591521248 file:minisign-0.9.tar.gz`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Signature: Signature{
|
||||
Algorithm: EdDSA,
|
||||
KeyID: 0xe7620f1842b4e81f,
|
||||
UntrustedComment: `signature from minisign secret key`,
|
||||
TrustedComment: `timestamp:1591521248 file:minisign-0.9.tar.gz`,
|
||||
Signature: [64]byte{20, 99, 118, 100, 132, 21, 202, 44, 47, 123, 240, 66, 228, 28, 175, 132, 143, 49, 11, 188, 252, 49, 53, 73, 106, 154, 66, 249, 67, 203, 35, 77, 156, 24, 226, 182, 244, 241, 252, 5, 244, 97, 127, 41, 191, 156, 128, 14, 117, 64, 157, 164, 36, 146, 238, 203, 151, 33, 174, 82, 239, 66, 73, 10},
|
||||
},
|
||||
},
|
||||
{
|
||||
Signature: Signature{
|
||||
Algorithm: EdDSA,
|
||||
KeyID: 0xe7620f1842b4e81f,
|
||||
UntrustedComment: `signature from minisign secret key`,
|
||||
TrustedComment: `timestamp:1591521248 file:minisign-0.9.tar.gz`,
|
||||
Signature: [64]byte{20, 99, 118, 100, 132, 21, 202, 44, 47, 123, 240, 66, 228, 28, 175, 132, 143, 49, 11, 188, 252, 49, 53, 73, 106, 154, 66, 249, 67, 203, 35, 77, 156, 24, 226, 182, 244, 241, 252, 5, 244, 97, 127, 41, 191, 156, 128, 14, 117, 64, 157, 164, 36, 146, 238, 203, 151, 33, 174, 82, 239, 66, 73, 10},
|
||||
CommentSignature: [64]byte{148, 178, 205, 92, 217, 151, 10, 78, 112, 147, 154, 17, 47, 24, 233, 136, 141, 16, 37, 217, 29, 77, 64, 75, 217, 55, 69, 178, 114, 188, 40, 93, 6, 130, 93, 121, 211, 7, 19, 198, 190, 160, 33, 49, 136, 129, 80, 249, 121, 170, 165, 216, 105, 97, 230, 151, 208, 109, 244, 227, 46, 121, 241, 15},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var unmarshalSignatureTests = []struct {
|
||||
Text string
|
||||
Signature Signature
|
||||
ShouldFail bool
|
||||
}{
|
||||
{
|
||||
Text: `untrusted comment: signature from minisign secret key
|
||||
RWQf6LRCGA9i5xRjdmSEFcosL3vwQuQcr4SPMQu8/DE1SWqaQvlDyyNNnBjitvTx/AX0YX8pv5yADnVAnaQkku7LlyGuUu9CSQo=
|
||||
trusted comment: timestamp:1591521248 file:minisign-0.9.tar.gz
|
||||
lLLNXNmXCk5wk5oRLxjpiI0QJdkdTUBL2TdFsnK8KF0Ggl150wcTxr6gITGIgVD5eaql2Glh5pfQbfTjLnnxDw==`,
|
||||
Signature: Signature{
|
||||
Algorithm: EdDSA,
|
||||
KeyID: 0xe7620f1842b4e81f,
|
||||
UntrustedComment: `signature from minisign secret key`,
|
||||
TrustedComment: `timestamp:1591521248 file:minisign-0.9.tar.gz`,
|
||||
Signature: [64]byte{20, 99, 118, 100, 132, 21, 202, 44, 47, 123, 240, 66, 228, 28, 175, 132, 143, 49, 11, 188, 252, 49, 53, 73, 106, 154, 66, 249, 67, 203, 35, 77, 156, 24, 226, 182, 244, 241, 252, 5, 244, 97, 127, 41, 191, 156, 128, 14, 117, 64, 157, 164, 36, 146, 238, 203, 151, 33, 174, 82, 239, 66, 73, 10},
|
||||
CommentSignature: [64]byte{148, 178, 205, 92, 217, 151, 10, 78, 112, 147, 154, 17, 47, 24, 233, 136, 141, 16, 37, 217, 29, 77, 64, 75, 217, 55, 69, 178, 114, 188, 40, 93, 6, 130, 93, 121, 211, 7, 19, 198, 190, 160, 33, 49, 136, 129, 80, 249, 121, 170, 165, 216, 105, 97, 230, 151, 208, 109, 244, 227, 46, 121, 241, 15},
|
||||
},
|
||||
},
|
||||
{
|
||||
Text: `untrusted comment: signature from minisign secret key
|
||||
RWQf6LRCGA9i5xRjdmSEFcosL3vwQuQcr4SPMQu8/DE1SWqaQvlDyyNNnBjitvTx/AX0YX8pv5yADnVAnaQkku7LlyGuUu9CSQo=
|
||||
trusted comment: timestamp:1591521248 file:minisign-0.9.tar.gz
|
||||
lLLNXNmXCk5wk5oRLxjpiI0QJdkdTUBL2TdFsnK8KF0Ggl150wcTxr6gITGIgVD5eaql2Glh5pfQbfTjLnnxDw==` + "\n\r\n",
|
||||
Signature: Signature{
|
||||
Algorithm: EdDSA,
|
||||
KeyID: 0xe7620f1842b4e81f,
|
||||
UntrustedComment: `signature from minisign secret key`,
|
||||
TrustedComment: `timestamp:1591521248 file:minisign-0.9.tar.gz`,
|
||||
Signature: [64]byte{20, 99, 118, 100, 132, 21, 202, 44, 47, 123, 240, 66, 228, 28, 175, 132, 143, 49, 11, 188, 252, 49, 53, 73, 106, 154, 66, 249, 67, 203, 35, 77, 156, 24, 226, 182, 244, 241, 252, 5, 244, 97, 127, 41, 191, 156, 128, 14, 117, 64, 157, 164, 36, 146, 238, 203, 151, 33, 174, 82, 239, 66, 73, 10},
|
||||
CommentSignature: [64]byte{148, 178, 205, 92, 217, 151, 10, 78, 112, 147, 154, 17, 47, 24, 233, 136, 141, 16, 37, 217, 29, 77, 64, 75, 217, 55, 69, 178, 114, 188, 40, 93, 6, 130, 93, 121, 211, 7, 19, 198, 190, 160, 33, 49, 136, 129, 80, 249, 121, 170, 165, 216, 105, 97, 230, 151, 208, 109, 244, 227, 46, 121, 241, 15},
|
||||
},
|
||||
},
|
||||
|
||||
// Invalid signatures
|
||||
{
|
||||
Text: `RWQf6LRCGA9i5xRjdmSEFcosL3vwQuQcr4SPMQu8/DE1SWqaQvlDyyNNnBjitvTx/AX0YX8pv5yADnVAnaQkku7LlyGuUu9CSQo=
|
||||
trusted comment: timestamp:1591521248 file:minisign-0.9.tar.gz
|
||||
lLLNXNmXCk5wk5oRLxjpiI0QJdkdTUBL2TdFsnK8KF0Ggl150wcTxr6gITGIgVD5eaql2Glh5pfQbfTjLnnxDw==`,
|
||||
ShouldFail: true, // Missing untrusted comment
|
||||
},
|
||||
{
|
||||
Text: `untrusted: signature from minisign secret key
|
||||
RWQf6LRCGA9i5xRjdmSEFcosL3vwQuQcr4SPMQu8/DE1SWqaQvlDyyNNnBjitvTx/AX0YX8pv5yADnVAnaQkku7LlyGuUu9CSQo=
|
||||
trusted comment: timestamp:1591521248 file:minisign-0.9.tar.gz
|
||||
lLLNXNmXCk5wk5oRLxjpiI0QJdkdTUBL2TdFsnK8KF0Ggl150wcTxr6gITGIgVD5eaql2Glh5pfQbfTjLnnxDw==`,
|
||||
ShouldFail: true, // Invalid untrusted comment - wrong prefix
|
||||
},
|
||||
|
||||
{
|
||||
Text: `untrusted comment: signature from minisign secret key
|
||||
trusted comment: timestamp:1591521248 file:minisign-0.9.tar.gz
|
||||
lLLNXNmXCk5wk5oRLxjpiI0QJdkdTUBL2TdFsnK8KF0Ggl150wcTxr6gITGIgVD5eaql2Glh5pfQbfTjLnnxDw==`,
|
||||
ShouldFail: true, // Missing signature value
|
||||
},
|
||||
{
|
||||
Text: `untrusted comment: signature from minisign secret key
|
||||
31TR+QBxE86BOJz1U46pc1lM1zEvMLBDTE255CHxFFLFcn4qPd3Q77xJTF2Y2IkDNqrTOCaZ43PQjSv9kIrnHXXwW0dwKnj
|
||||
trusted comment: timestamp:1591521248 file:minisign-0.9.tar.gz
|
||||
lLLNXNmXCk5wk5oRLxjpiI0QJdkdTUBL2TdFsnK8KF0Ggl150wcTxr6gITGIgVD5eaql2Glh5pfQbfTjLnnxDw==`,
|
||||
ShouldFail: true, // Invalid signature value - invalid base64
|
||||
},
|
||||
{
|
||||
Text: `untrusted comment: signature from minisign secret key
|
||||
f4IYNY3p6K5CYtfB+dhN6Y+Fi+F6wWI0r+VjLwDE0q23wB1Opso6w/MJd9YGIU/HBs04flXnak37x/s2QhWAZlSCdbQYX7Q=
|
||||
trusted comment: timestamp:1591521248 file:minisign-0.9.tar.gz
|
||||
lLLNXNmXCk5wk5oRLxjpiI0QJdkdTUBL2TdFsnK8KF0Ggl150wcTxr6gITGIgVD5eaql2Glh5pfQbfTjLnnxDw==`,
|
||||
ShouldFail: true, // Invalid signature value - invalid size
|
||||
},
|
||||
|
||||
{
|
||||
Text: `untrusted comment: signature from minisign secret key
|
||||
RWQf6LRCGA9i5xRjdmSEFcosL3vwQuQcr4SPMQu8/DE1SWqaQvlDyyNNnBjitvTx/AX0YX8pv5yADnVAnaQkku7LlyGuUu9CSQo=
|
||||
lLLNXNmXCk5wk5oRLxjpiI0QJdkdTUBL2TdFsnK8KF0Ggl150wcTxr6gITGIgVD5eaql2Glh5pfQbfTjLnnxDw==` + "\n\r\n",
|
||||
ShouldFail: true, // Missing trusted comment
|
||||
},
|
||||
{
|
||||
Text: `untrusted comment: signature from minisign secret key
|
||||
RWQf6LRCGA9i5xRjdmSEFcosL3vwQuQcr4SPMQu8/DE1SWqaQvlDyyNNnBjitvTx/AX0YX8pv5yADnVAnaQkku7LlyGuUu9CSQo=
|
||||
comment: timestamp:1591521248 file:minisign-0.9.tar.gz
|
||||
lLLNXNmXCk5wk5oRLxjpiI0QJdkdTUBL2TdFsnK8KF0Ggl150wcTxr6gITGIgVD5eaql2Glh5pfQbfTjLnnxDw==` + "\n\r\n",
|
||||
ShouldFail: true, // Invalid trusted comment - wrong prefix
|
||||
},
|
||||
|
||||
{
|
||||
Text: `untrusted comment: signature from minisign secret key
|
||||
RWQf6LRCGA9i5xRjdmSEFcosL3vwQuQcr4SPMQu8/DE1SWqaQvlDyyNNnBjitvTx/AX0YX8pv5yADnVAnaQkku7LlyGuUu9CSQo=
|
||||
trusted comment: timestamp:1591521248 file:minisign-0.9.tar.gz`,
|
||||
ShouldFail: true, // Missing comment signature
|
||||
},
|
||||
{
|
||||
Text: `untrusted comment: signature from minisign secret key
|
||||
RWQf6LRCGA9i5xRjdmSEFcosL3vwQuQcr4SPMQu8/DE1SWqaQvlDyyNNnBjitvTx/AX0YX8pv5yADnVAnaQkku7LlyGuUu9CSQo=
|
||||
trusted comment: timestamp:1591521248 file:minisign-0.9.tar.gz
|
||||
Bqq219+sDloDkxHiCLcR5sTxrbl+qMS4oEnZ+IrZ4JDH5BxAzKehjoWSch3nbyNT96c/jz+XQjj4zd492skB_w==`,
|
||||
ShouldFail: true, // Invalid comment signature - invalid base64
|
||||
},
|
||||
{
|
||||
Text: `untrusted comment: signature from minisign secret key
|
||||
RWQf6LRCGA9i5xRjdmSEFcosL3vwQuQcr4SPMQu8/DE1SWqaQvlDyyNNnBjitvTx/AX0YX8pv5yADnVAnaQkku7LlyGuUu9CSQo=
|
||||
trusted comment: timestamp:1591521248 file:minisign-0.9.tar.gz
|
||||
nqGtUS55Xhx/VzvCGtWjtsnlcItcsp0hzl/40j3oRkyJAISXHTakVQKK2VBBMyjBfhZTRRlEputvn/dNdC/Dh6Y=`,
|
||||
ShouldFail: true, // Invalid comment signature - invalid size
|
||||
},
|
||||
}
|
||||
Vendored
Vendored
Vendored
Reference in New Issue
Block a user