@@ -3,6 +3,7 @@ package pgp
import (
"bytes"
"crypto"
+ "crypto/rand"
"encoding/binary"
"fmt"
"io"
@@ -23,6 +24,8 @@ import (
openpgp "cunicu.li/go-openpgp-card"
)
+var randRead = rand.Read
+
// openCard connects to the first available OpenPGP smartcard via PC/SC.
func openCard() (*openpgp.Card, error) {
ctx, err := scard.EstablishContext()
@@ -94,7 +97,7 @@ func BuildPGPSignedMessage(payload []byte, pin string, publicKeyPath string) ([]
headers, body := splitPayload(payload)
// Build the signed body part (this is what gets hashed)
- boundary := fmt.Sprintf("----=_Part_%d", time.Now().Unix())
+ boundary := generateMIMEBoundary()
signedPart := buildSignedPart(headers, body, boundary)
// Build the OpenPGP signature packet
@@ -112,6 +115,14 @@ func BuildPGPSignedMessage(payload []byte, pin string, publicKeyPath string) ([]
return buildMultipartSigned(headers, body, boundary, armoredSig), nil
}
+func generateMIMEBoundary() string {
+ var buf [16]byte
+ if n, err := randRead(buf[:]); err == nil && n == len(buf) {
+ return fmt.Sprintf("----=_Part_%x", buf[:])
+ }
+ return fmt.Sprintf("----=_Part_%d", time.Now().UnixNano())
+}
+
// loadSigningPublicKey reads a PGP public key file and returns the signing
// subkey's PublicKey (or the primary key if no signing subkey exists).
func loadSigningPublicKey(path string) (*packet.PublicKey, error) {
@@ -1,6 +1,8 @@
package pgp
import (
+ "errors"
+ "strconv"
"strings"
"testing"
)
@@ -72,3 +74,39 @@ func TestParseASN1Signature_WellFormed(t *testing.T) {
t.Errorf("s = %x, want 02", s)
}
}
+
+func TestGenerateMIMEBoundaryUsesCryptoRandomBytes(t *testing.T) {
+ oldRandRead := randRead
+ defer func() { randRead = oldRandRead }()
+
+ randRead = func(p []byte) (int, error) {
+ for i := range p {
+ p[i] = byte(i)
+ }
+ return len(p), nil
+ }
+
+ got := generateMIMEBoundary()
+ want := "----=_Part_000102030405060708090a0b0c0d0e0f"
+ if got != want {
+ t.Fatalf("boundary = %q, want %q", got, want)
+ }
+}
+
+func TestGenerateMIMEBoundaryFallsBackToUnixNano(t *testing.T) {
+ oldRandRead := randRead
+ defer func() { randRead = oldRandRead }()
+
+ randRead = func(_ []byte) (int, error) {
+ return 0, errors.New("random source unavailable")
+ }
+
+ const prefix = "----=_Part_"
+ got := generateMIMEBoundary()
+ if !strings.HasPrefix(got, prefix) {
+ t.Fatalf("boundary = %q, want prefix %q", got, prefix)
+ }
+ if _, err := strconv.ParseInt(strings.TrimPrefix(got, prefix), 10, 64); err != nil {
+ t.Fatalf("fallback boundary suffix is not a UnixNano timestamp: %v", err)
+ }
+}