@ -3,20 +3,19 @@ package sigcred
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"errors"
"fmt"
"io"
"io/ioutil"
"os/exec"
"path/filepath"
"strings"
"time"
"dehub.dev/src/dehub.git/fs"
"dehub.dev/src/dehub.git/yamlutil"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
)
@ -42,39 +41,60 @@ func (c *CredentialPGPSignature) SelfVerify(data []byte) error {
return sig . Verify ( nil , data , Credential { PGPSignature : c } )
}
type pgpPub Key struct {
pubK ey * packet . PublicKe y
type pgpKey struct {
entit y * openpgp . Entit y
}
func newPGPPubKey ( r io . Reader ) ( pgpPub Key , error ) {
func newPGPPubKey ( r io . Reader ) ( pgpKey , error ) {
// TODO support non-armored keys as well
block , err := armor . Decode ( r )
if err != nil {
return pgpPub Key { } , fmt . Errorf ( "could not decode armored PGP public key: %w" , err )
return pgpKey { } , fmt . Errorf ( "could not decode armored PGP public key: %w" , err )
}
pkt , err := packet . Read ( block . Body )
entity , err := openpgp . ReadEntity ( packet . New Reader ( block . Body ) )
if err != nil {
return pgpPub Key { } , fmt . Errorf ( "could not read PGP public key: %w" , err )
return pgpKey { } , fmt . Errorf ( "could not read PGP public key: %w" , err )
}
return pgpKey { entity : entity } , nil
}
pubKey , ok := pkt . ( * packet . PublicKey )
if ! ok {
return pgpPubKey { } , fmt . Errorf ( "packet is not a public key, it's a %T" , pkt )
func ( s pgpKey ) Sign ( _ fs . FS , data [ ] byte ) ( Credential , error ) {
if s . entity . PrivateKey == nil {
return Credential { } , errors . New ( "private key not loaded" )
}
h := sha256 . New ( )
h . Write ( data )
var sig packet . Signature
sig . Hash = crypto . SHA256
sig . PubKeyAlgo = s . entity . PrimaryKey . PubKeyAlgo
if err := sig . Sign ( h , s . entity . PrivateKey , nil ) ; err != nil {
return Credential { } , fmt . Errorf ( "signing data: %w" , err )
}
body := new ( bytes . Buffer )
if err := sig . Serialize ( body ) ; err != nil {
return Credential { } , fmt . Errorf ( "serializing signature: %w" , err )
}
return pgpPubKey { pubKey : pubKey } , nil
return Credential {
PGPSignature : & CredentialPGPSignature {
PubKeyID : s . entity . PrimaryKey . KeyIdString ( ) ,
Body : body . Bytes ( ) ,
} ,
} , nil
}
func ( s pgpPubKey ) Signed ( _ fs . FS , cred Credential ) ( bool , error ) {
func ( s pgpKey ) Signed ( _ fs . FS , cred Credential ) ( bool , error ) {
if cred . PGPSignature == nil {
return false , nil
}
return cred . PGPSignature . PubKeyID == s . pubKey . KeyIdString ( ) , nil
return cred . PGPSignature . PubKeyID == s . entity . Primary Key. KeyIdString ( ) , nil
}
func ( s pgpPubKey ) Verify ( _ fs . FS , data [ ] byte , cred Credential ) error {
func ( s pgpKey ) Verify ( _ fs . FS , data [ ] byte , cred Credential ) error {
credSig := cred . PGPSignature
if credSig == nil {
return fmt . Errorf ( "SignifierPGPFile cannot verify %+v" , cred )
@ -95,15 +115,15 @@ func (s pgpPubKey) Verify(_ fs.FS, data []byte, cred Credential) error {
// package expects you to do it yourself.
h := sigPkt . Hash . New ( )
h . Write ( data )
return s . pub Key. VerifySignature ( h , sigPkt )
return s . entity . Primary Key. VerifySignature ( h , sigPkt )
}
func ( s pgpPub Key ) encode ( ) ( [ ] byte , error ) {
func ( s pgpKey ) MarshalBinary ( ) ( [ ] byte , error ) {
body := new ( bytes . Buffer )
armorEncoder , err := armor . Encode ( body , "PGP PUBLIC KEY" , nil )
if err != nil {
return nil , fmt . Errorf ( "error initializing armor encoder: %w" , err )
} else if err := s . pubK ey. Serialize ( armorEncoder ) ; err != nil {
} else if err := s . entit y . Serialize ( armorEncoder ) ; err != nil {
return nil , fmt . Errorf ( "error encoding public key: %w" , err )
} else if err := armorEncoder . Close ( ) ; err != nil {
return nil , fmt . Errorf ( "error closing armor encoder: %w" , err )
@ -111,68 +131,27 @@ func (s pgpPubKey) encode() ([]byte, error) {
return body . Bytes ( ) , nil
}
func ( s pgpPubKey ) asSignfier ( ) ( SignifierPGP , error ) {
body , err := s . encode ( )
if err != nil {
return SignifierPGP { } , err
}
return SignifierPGP {
Body : string ( body ) ,
} , nil
}
type pgpPrivKey struct {
pgpPubKey
privKey * packet . PrivateKey
}
// SignifierPGPTmp returns a direct implementation of the SignifierInterface
// TestSignifierPGP returns a direct implementation of the SignifierInterface
// which uses a random private key generated in memory, as well as an armored
// version of its public key.
func SignifierPGPTmp ( accountID string , randReader io . Reader ) ( SignifierInterface , [ ] byte ) {
rawPrivKey , err := ecdsa . GenerateKey ( elliptic . P521 ( ) , randReader )
//
// NOTE that the key returned is very weak, and should only be used for tests.
func TestSignifierPGP ( accountID string , randReader io . Reader ) ( SignifierInterface , [ ] byte ) {
entity , err := openpgp . NewEntity ( accountID , "" , accountID + "@example.com" , & packet . Config {
Rand : randReader ,
RSABits : 512 ,
} )
if err != nil {
panic ( err )
}
privKeyRaw := packet . NewECDSAPrivateKey ( time . Now ( ) , rawPrivKey )
privKey := pgpPrivKey {
pgpPubKey : pgpPubKey {
pubKey : & privKeyRaw . PublicKey ,
} ,
privKey : privKeyRaw ,
}
pubKeyBody , err := privKey . pgpPubKey . encode ( )
pgpKey := pgpKey { entity : entity }
pubKeyBody , err := pgpKey . MarshalBinary ( )
if err != nil {
panic ( err )
}
return accountSignifier ( accountID , privKey ) , pubKeyBody
}
func ( s pgpPrivKey ) Sign ( _ fs . FS , data [ ] byte ) ( Credential , error ) {
h := sha256 . New ( )
h . Write ( data )
var sig packet . Signature
sig . Hash = crypto . SHA256
sig . PubKeyAlgo = s . pubKey . PubKeyAlgo
if err := sig . Sign ( h , s . privKey , nil ) ; err != nil {
return Credential { } , fmt . Errorf ( "failed to sign data: %w" , err )
}
body := new ( bytes . Buffer )
if err := sig . Serialize ( body ) ; err != nil {
return Credential { } , fmt . Errorf ( "failed to serialize signature: %w" , err )
}
return Credential {
PGPSignature : & CredentialPGPSignature {
PubKeyID : s . pubKey . KeyIdString ( ) ,
Body : body . Bytes ( ) ,
} ,
} , nil
return accountSignifier ( accountID , pgpKey ) , pubKeyBody
}
// SignifierPGP describes a pgp public key whose corresponding private key will
@ -203,12 +182,12 @@ func cmdGPG(stdin []byte, args ...string) ([]byte, error) {
return out , nil
}
// SignifierPGPFromKeyID loads a pgp key using the given identifier. The key is
// assumed to be stored. in the client's keyring already.
// Load SignifierPGP loads a pgp key using the given identifier. The key is
// assumed to be stored in the client's keyring already.
//
// If setPubKeyBody is true, then CredentialPGPSignature instances produced by
// the returned Signifier will have their PubKeyBody field set.
func SignifierPGPFromKeyID ( keyID string , setPubKeyBody bool ) ( SignifierInterface , error ) {
func Load SignifierPGP( keyID string , setPubKeyBody bool ) ( SignifierInterface , error ) {
pubKey , err := cmdGPG ( nil , "-a" , "--export" , keyID )
if err != nil {
return nil , fmt . Errorf ( "loading public key: %w" , err )
@ -225,7 +204,7 @@ func SignifierPGPFromKeyID(keyID string, setPubKeyBody bool) (SignifierInterface
return sigInt , nil
}
func ( s SignifierPGP ) load ( fs fs . FS ) ( pgpPub Key , error ) {
func ( s SignifierPGP ) load ( fs fs . FS ) ( pgpKey , error ) {
if s . Body != "" {
return newPGPPubKey ( strings . NewReader ( s . Body ) )
}
@ -233,13 +212,13 @@ func (s SignifierPGP) load(fs fs.FS) (pgpPubKey, error) {
path := filepath . Clean ( s . Path )
fr , err := fs . Open ( path )
if err != nil {
return pgpPub Key { } , fmt . Errorf ( "opening PGP public key file at %q: %w" , path , err )
return pgpKey { } , fmt . Errorf ( "opening PGP public key file at %q: %w" , path , err )
}
defer fr . Close ( )
pubKeyB , err := ioutil . ReadAll ( fr )
if err != nil {
return pgpPub Key { } , fmt . Errorf ( "reading PGP public key from file at %q: %w" , s . Path , err )
return pgpKey { } , fmt . Errorf ( "reading PGP public key from file at %q: %w" , s . Path , err )
}
return SignifierPGP { Body : string ( pubKeyB ) } . load ( fs )
@ -253,14 +232,15 @@ func (s SignifierPGP) Sign(fs fs.FS, data []byte) (Credential, error) {
return Credential { } , err
}
sig , err := cmdGPG ( data , "--detach-sign" , "--local-user" , sigPGP . pubKey . KeyIdString ( ) )
keyID := sigPGP . entity . PrimaryKey . KeyIdString ( )
sig , err := cmdGPG ( data , "--detach-sign" , "--local-user" , keyID )
if err != nil {
return Credential { } , fmt . Errorf ( "signing with pgp key: %w" , err )
}
return Credential {
PGPSignature : & CredentialPGPSignature {
PubKeyID : sigPGP . pubKey . KeyIdString ( ) ,
PubKeyID : keyID ,
Body : sig ,
} ,
} , nil