From 58c7358113a2dfc456100f20dbd5db3b1ff996d4 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 14 Jun 2018 00:41:45 +0300 Subject: [PATCH] Unit tests for PGP signing/verification These unit-tests cover operations via both PGP providers: built-in `openpgp` and external `gpg`. Next step is to run these tests for gpg1 & gpg2 as separate entities. --- pgp/gnupg.go | 14 +--- pgp/gnupg_test.go | 32 +++++++ pgp/internal_test.go | 87 ++++--------------- pgp/keyrings/aptly.pub | Bin 0 -> 915 bytes pgp/keyrings/aptly.sec | Bin 0 -> 977 bytes pgp/keyrings/aptly_passphrase.pub | Bin 0 -> 915 bytes pgp/keyrings/aptly_passphrase.sec | Bin 0 -> 1052 bytes pgp/sign_test.go | 134 ++++++++++++++++++++++++++++++ pgp/verify_test.go | 93 +++++++++++++++++++++ 9 files changed, 274 insertions(+), 86 deletions(-) create mode 100644 pgp/keyrings/aptly.pub create mode 100644 pgp/keyrings/aptly.sec create mode 100644 pgp/keyrings/aptly_passphrase.pub create mode 100644 pgp/keyrings/aptly_passphrase.sec create mode 100644 pgp/sign_test.go create mode 100644 pgp/verify_test.go diff --git a/pgp/gnupg.go b/pgp/gnupg.go index 5d416281..ba5f24b3 100644 --- a/pgp/gnupg.go +++ b/pgp/gnupg.go @@ -174,7 +174,7 @@ type GpgVerifier struct { keyRings []string } -// NewGpgVerifier creates a new gpg signer +// NewGpgVerifier creates a new gpg verifier func NewGpgVerifier() *GpgVerifier { gpg, err := findGPG1() if err != nil { @@ -191,18 +191,6 @@ func NewGpgVerifier() *GpgVerifier { // InitKeyring verifies that gpg is installed and some keys are trusted func (g *GpgVerifier) InitKeyring() error { - cmd, err := findGPG1() - if err != nil { - return err - } - g.gpg = cmd - - cmd, err = findGPGV1() - if err != nil { - return err - } - g.gpgv = cmd - if len(g.keyRings) == 0 { // using default keyring output, err := exec.Command(g.gpg, "--no-default-keyring", "--no-auto-check-trustdb", "--keyring", "trustedkeys.gpg", "--list-keys").Output() diff --git a/pgp/gnupg_test.go b/pgp/gnupg_test.go index 670ab114..30e93ca3 100644 --- a/pgp/gnupg_test.go +++ b/pgp/gnupg_test.go @@ -77,3 +77,35 @@ func (s *GnupgSuite) TestGPGVNothing(c *C) { c.Assert(func() { NewGpgVerifier() }, PanicMatches, `Couldn't find a suitable gpgv executable.+`) } + +type Gnupg1VerifierSuite struct { + VerifierSuite +} + +var _ = Suite(&Gnupg1VerifierSuite{}) + +func (s *Gnupg1VerifierSuite) SetUpTest(c *C) { + s.verifier = NewGpgVerifier() + s.verifier.AddKeyring("./trusted.gpg") + + c.Assert(s.verifier.InitKeyring(), IsNil) +} + +type Gnupg1SignerSuite struct { + SignerSuite +} + +var _ = Suite(&Gnupg1SignerSuite{}) + +func (s *Gnupg1SignerSuite) SetUpTest(c *C) { + s.signer = NewGpgSigner() + s.signer.SetBatch(true) + + s.verifier = &GoVerifier{} + s.verifier.AddKeyring("./keyrings/aptly.pub") + s.verifier.AddKeyring("./keyrings/aptly_passphrase.pub") + + c.Assert(s.verifier.InitKeyring(), IsNil) + + s.SignerSuite.SetUpTest(c) +} diff --git a/pgp/internal_test.go b/pgp/internal_test.go index 772e52bd..79fd60d5 100644 --- a/pgp/internal_test.go +++ b/pgp/internal_test.go @@ -1,14 +1,11 @@ package pgp import ( - "io/ioutil" - "os" - . "gopkg.in/check.v1" ) type GoVerifierSuite struct { - verifier Verifier + VerifierSuite } var _ = Suite(&GoVerifierSuite{}) @@ -20,77 +17,21 @@ func (s *GoVerifierSuite) SetUpTest(c *C) { c.Assert(s.verifier.InitKeyring(), IsNil) } -func (s *GoVerifierSuite) TestVerifyDetached(c *C) { - for _, test := range []struct { - textName, signatureName string - }{ - {"1.text", "1.signature"}, - {"2.text", "2.signature"}, - {"3.text", "3.signature"}, - } { - cleartext, err := os.Open(test.textName) - c.Assert(err, IsNil) - - signature, err := os.Open(test.signatureName) - c.Assert(err, IsNil) - - err = s.verifier.VerifyDetachedSignature(signature, cleartext, false) - c.Assert(err, IsNil) - - signature.Close() - cleartext.Close() - } +type GoSignerSuite struct { + SignerSuite } -func (s *GoVerifierSuite) TestVerifyClearsigned(c *C) { - for _, test := range []struct { - clearSignedName string - }{ - {"1.clearsigned"}, - } { - clearsigned, err := os.Open(test.clearSignedName) - c.Assert(err, IsNil) +var _ = Suite(&GoSignerSuite{}) - keyInfo, err := s.verifier.VerifyClearsigned(clearsigned, false) - c.Assert(err, IsNil) - c.Check(keyInfo.GoodKeys, DeepEquals, []Key{"8B48AD6246925553", "7638D0442B90D010"}) - c.Check(keyInfo.MissingKeys, DeepEquals, []Key(nil)) +func (s *GoSignerSuite) SetUpTest(c *C) { + s.signer = &GoSigner{} + s.signer.SetBatch(true) - clearsigned.Close() - } -} - -func (s *GoVerifierSuite) TestExtractClearsigned(c *C) { - for _, test := range []struct { - clearSignedName, clearTextName string - }{ - {"1.clearsigned", "1.cleartext"}, - } { - clearsigned, err := os.Open(test.clearSignedName) - c.Assert(err, IsNil) - - cleartext, err := os.Open(test.clearTextName) - c.Assert(err, IsNil) - - is, err := s.verifier.IsClearSigned(clearsigned) - c.Assert(err, IsNil) - c.Check(is, Equals, true) - - clearsigned.Seek(0, 0) - - extractedF, err := s.verifier.ExtractClearsigned(clearsigned) - c.Assert(err, IsNil) - - expected, err := ioutil.ReadAll(cleartext) - c.Assert(err, IsNil) - - extracted, err := ioutil.ReadAll(extractedF) - c.Assert(err, IsNil) - - c.Check(expected, DeepEquals, extracted) - - extractedF.Close() - clearsigned.Close() - cleartext.Close() - } + s.verifier = &GoVerifier{} + s.verifier.AddKeyring("./keyrings/aptly.pub") + s.verifier.AddKeyring("./keyrings/aptly_passphrase.pub") + + c.Assert(s.verifier.InitKeyring(), IsNil) + + s.SignerSuite.SetUpTest(c) } diff --git a/pgp/keyrings/aptly.pub b/pgp/keyrings/aptly.pub new file mode 100644 index 0000000000000000000000000000000000000000..08758e4302de6ce4f49a2af5c44c3933784c7aaa GIT binary patch literal 915 zcmV;E18n@60ipy_`=yT&1OU0Xct7+Dh9|KF?G`P|&qxfns=ia+?_C}Z3Q77aY&oDm z=DPPGT}v`rhosG`k(mHKkP*T<;A>!VCtNV^wrbkr>Li1NuVz(`hR_)ne z3th6NpdiQ7<Fwy!G{GEu6DDvIH>J2*jMl!ZAe17%%=dLdNX9~nLZfdb`w!OtYzEZ zU$tG7+;oa-r;o+m6H6^)RKfTUEn;!>+{aCdpam;laCrjBCQ$?bPc%>)c$RkTb8gOo zrva@giIB^900*$mRq4tsG9J{_hH8zMD=;ruT--<4KXg9&0lu4`2*h(Q_s&%G^(Qn2 zVuFZYUd@|3-K+MvV;N!hz-S>vtdu82MEjJB_U#G=RRF%3O;1~3hdN8HZHx{C9 z1CNbQHrRY!)*y&Ov@Jn!bZmJbRAqB?WpW@WWN&UKbRczeWguyEDIh#_Wpi{uVQ_S8 zc`j*gW^X=-VgwTr0stZf0#f^>j{+Mb1`7!Y2Ll2I6$kzyoEjx*d~P+M;6PgR0^f+5npe&Ob>g0mP-)vE<&{=?mIr z23Ur<0SyFF`=yT%1OW6E{*kf_=~7suCOy4D*L(qZ6Ku9KyXE#t+4?gNe8$M_(Kmq- zP;Yssfh76u=2mM8H4M=@hjQBhqbgdyn+ihl14|aY*z!=QmG;cwgo~FYwKN))*q5??(Uw1q1-f`}d!7QG;m{ zd-x)&h$fKs*|h%_ERc**yvS^~>Cd~Ej{+ME0162ZA=|i|7TZ2;IfekBy@su2 pbXyVDebzEULM=)oxk=@Z0G>PR9ZewfiG<^n85QUm&on6Zx23xEox=bC literal 0 HcmV?d00001 diff --git a/pgp/keyrings/aptly.sec b/pgp/keyrings/aptly.sec new file mode 100644 index 0000000000000000000000000000000000000000..f90e1c913181ff15a8dc9434d9b43a64ccefee16 GIT binary patch literal 977 zcmV;?11|iP0lNfJ`=yT&1OU0Xct7+Dh9|KF?G`P|&qxfns=ia+?_C}Z3Q77aY&oDm z=DPPGT}v`rhosG`k(mHKkP*T<;A>!VCtNV^wrbkr>Li1NuVz(`hR_)ne z3th6NpdiQ7<Fwy!G{GEu6DDvIH>J2*jMl!ZAe17%%=dLdNX9~nLZfdb`w!OtYzEZ zU$tG7+;oa-r;o+m6H6^)RKfTUEn;!>+{aCdpam;laCrjBCQ$?bPc%>)c$RkTb8gOo zrva@giIB^900*$mRq4tsG9J{_hH8zMD=;ruT--<4KXg9&0lu4`2*h(Q_s&%G^(Qn2 zVuFZYUd@|3-K+MvV;N!hz-S>vtdu82MEjJB_U#G=RRF%3O;1~3hdN8HZHx{C9 z1CNbQHrRY!)*y&O005w)1dfm)xUZvne1p<;!PnDwZ9?-4)U+)@aCB^WAXH^@bY*fN zC}eMLCv+fnb7dfDbSWS_bY*jNKw)rnY0HE3a(ZB;`uDTtQSlXgu zM=odGfgQv0Qk5Cj1975qb5DQ zLDzf%coS^4GrQ&XN!j`{4}8YR?a?=Z5>Rh>r-3B-?dDc%3N;MTI)`%G9>92-n&|a% z)LlaXfply-t^?#vGJ^2g{?cK~Krj|0&Rn4^{p$0Xv3rm7)`x=C9dStXI_Eb}gm_=( zKriske%2Twp6^Eh0|f*C%KP`9b5Vn76MOg~tB59$_Sv-m7A%mAQM|}(x9QKjnB(_V zXtb9;au%wdN7F+V;=@Y=go{bR&2wgoV)FVDFfpOyZ35#g;O5Kp{lK80cnONvC==;` zMkJTzP8M|J&oob%LA4M)&u6GkeK2k=j4ojRt+6foRS$h2I9E${008;|bEM3c%`#AN zru560gfdK6&xeM|gQ~xG>`LCTiFnl$FNjG57!d*h2?YXD`=yTp8w>yn2@oOMxSST- zK5aRM0H0M8G=^ntPd5ey_o*~SD*~Mfd4vF;U8*?cN<@Qwtw{io%pDp^eS*?nqr;}I-q#?f(6RWI z-OXcvP^lCq*Y(mz1OSYhefdk_v|koHb|0LjoxH?qTlgA$bn7b870Rh(E1cglq~G{> zWL?y~4}83gH#RycF+D%8ydsilZBTai=zl~H98lZSjS(v;$SGmeyYy8*$-j|mLziOa z{obdM51&ogV40AocwqWy+AlT_X#AfTRq$3*Tw5Ulo75{ z5GH^K+7jr|XFr#NX?6%so!OiZh!Wm`05$wkSQradtZH3SskB4Zw%f(MlI2R-7a=l+ z{8{$?w!RKVk-wSPG5bS^_ZEKw8Ne}Uuq?ynS-DDNnLJuM7R5qGzPE3U5p_#<2X?R+ zuJ3`wrlz5JMxYP|v@Jn!bZmJbRAqB?WpW@WWN&UKbRczeWguyEDIh#_Wpi{uVQ_S8 zc`j*gW^X=-VgwTr0stZf0#pF_hXNZT1`7!Y2Ll2I6$kfGlbf zPQGN+gxEe1^{B*lQvX=Ds$?0p8=xACrv5hEUyUMX#wm|s_X7X}1_S`N5*%@3y6^74 z;EF*YS!LmMi4Gt({SiHgpC!7P5z)S3^=$Wb)i;Na;L}$HUX!z_VwFNdy=X0ssjG0#pF_hXNZ60162Z^A3!;&E6{bVvhizhW1^F pQh@M!p0s?4b08k6=4vgD0G>G%4Dudkh>m4~)&`HDtX0QWgp6=$od5s; literal 0 HcmV?d00001 diff --git a/pgp/keyrings/aptly_passphrase.sec b/pgp/keyrings/aptly_passphrase.sec new file mode 100644 index 0000000000000000000000000000000000000000..2ffe24ef89c4e7d10037073e8862f7503f8618cc GIT binary patch literal 1052 zcmV+%1mpXa0pSEx0QiRy1OSd4WNFxt1~o!-S1@skL_54;9Geppna?(rQ9M6;^61m0 zTSc%gL&7ChBb7d0Or!C1EV9=15fqC^yng{ATtzFu#5gJRR62G-ad;H|u8fW1KTwD| zCDAtEgtlGHCcZom-PUPIqgO@njh~(Gx=D-y-tNOz))K;%IhEXR8>;}I-q#?f(6RWI z-OXcvP^lCq*Y(mz1OSYhefdk_v|koHb|0LjoxH?qTlgA$bn7b870Rh(E1cglq~G{> zWL?y~4}83gH#RycF+D%8ydsilZBTai=zl~H98lZSjS(v;$SGmeyYy8*$-j|mLziOa z{obdM51&ogV40AocwqWy+AlT_X#AfTRq$3*Tw5Ulo75{ z5GH^K+7jr|XFr#NX?6%so!OiZh!Wm`05$wkSQradtZH3SskB4Zw%f(MlI2R-7a=l+ z{8{$?w!RKVk-wSPG5bS^_ZEKw8Ne}Uuq?ynS-DDNnLJuM7R5qGzPE3U5p_#<2X?R+ zuJ3`wrlz5JMxYP|{sRL7D=XB?HUmj|U^$$e&t2|=B_YXad=mkJ=kX&cY2vWjGN3nB zD-j=-J1wYJT@3T@DFIFtwk?epcH;rGEkST}Yyg0vjU+3ke7Z0|EvW2m%QT3j`Jd z0|5da0Rk6*0162Z^A3!;&E6{bMJNEA4<(}n&Y3^*){v!(-Bs8-C|2#O0HCM&2wQDL zL1o(Y!zdTRXwe3#C+wX8R|Hf5_=gY#0O}1zeu71#Pm+V%W03BEpY+~@YNsmzLu}9< z&~xP=E_?QwN@)RxScujUPBN7AA^+HG%i6s$B3mGKZ5tj*`G&8FiOB@0g)rSHZ@?Eg zK@T)4WW2ZO_6d|uzGT#d*gg>TsKje>Z zf0g#1l9hhR0AF)>qD$GcIyY`KhK&{VxJh?n50SC%vbZd5af68kggg`TG9C524rc0esPistbb8{ZDgdC~ W)GO}0JmQ4yFg1%k_1g1bkD$UqR@;aG literal 0 HcmV?d00001 diff --git a/pgp/sign_test.go b/pgp/sign_test.go new file mode 100644 index 00000000..583329e7 --- /dev/null +++ b/pgp/sign_test.go @@ -0,0 +1,134 @@ +package pgp + +import ( + "crypto/rand" + "io" + "io/ioutil" + "os" + "path" + + . "gopkg.in/check.v1" +) + +// Common set of tests shared by internal & external GnuPG implementations +type SignerSuite struct { + signer Signer + verifier Verifier + + clearF *os.File + signedF *os.File + cleartext []byte + + passwordFile string +} + +func (s *SignerSuite) SetUpTest(c *C) { + tempDir := c.MkDir() + + var err error + s.clearF, err = os.Create(path.Join(tempDir, "cleartext")) + c.Assert(err, IsNil) + + s.cleartext = make([]byte, 0, 1024) + _, err = rand.Read(s.cleartext) + c.Assert(err, IsNil) + + _, err = s.clearF.Write(s.cleartext) + c.Assert(err, IsNil) + + _, err = s.clearF.Seek(0, io.SeekStart) + c.Assert(err, IsNil) + + s.signedF, err = os.Create(path.Join(tempDir, "signed")) + c.Assert(err, IsNil) + + s.passwordFile = path.Join(tempDir, "password") + f, err := os.OpenFile(s.passwordFile, os.O_CREATE|os.O_WRONLY, 0600) + c.Assert(err, IsNil) + + _, err = f.Write([]byte("verysecret")) + c.Assert(err, IsNil) + + f.Close() + + s.signer.SetBatch(true) +} + +func (s *SignerSuite) TearDownTest(c *C) { + s.clearF.Close() + s.signedF.Close() +} + +func (s *SignerSuite) testSignDetached(c *C) { + c.Assert(s.signer.Init(), IsNil) + + err := s.signer.DetachedSign(s.clearF.Name(), s.signedF.Name()) + c.Assert(err, IsNil) + + err = s.verifier.VerifyDetachedSignature(s.signedF, s.clearF, false) + c.Assert(err, IsNil) +} + +func (s *SignerSuite) TestSignDetachedNoPassphrase(c *C) { + s.signer.SetKeyRing("keyrings/aptly.pub", "keyrings/aptly.sec") + + s.testSignDetached(c) +} + +func (s *SignerSuite) TestSignDetachedPassphrase(c *C) { + s.signer.SetKeyRing("keyrings/aptly_passphrase.pub", "keyrings/aptly_passphrase.sec") + s.signer.SetPassphrase("verysecret", "") + + s.testSignDetached(c) +} + +func (s *SignerSuite) TestSignDetachedPassphraseFile(c *C) { + s.signer.SetKeyRing("keyrings/aptly_passphrase.pub", "keyrings/aptly_passphrase.sec") + s.signer.SetPassphrase("", s.passwordFile) + + s.testSignDetached(c) +} + +func (s *SignerSuite) testClearSign(c *C, expectedKey Key) { + c.Assert(s.signer.Init(), IsNil) + + err := s.signer.ClearSign(s.clearF.Name(), s.signedF.Name()) + c.Assert(err, IsNil) + + keyInfo, err := s.verifier.VerifyClearsigned(s.signedF, false) + c.Assert(err, IsNil) + + c.Assert(keyInfo.GoodKeys, DeepEquals, []Key{expectedKey}) + c.Assert(keyInfo.MissingKeys, DeepEquals, []Key(nil)) + + _, err = s.signedF.Seek(0, io.SeekStart) + c.Assert(err, IsNil) + extractedF, err := s.verifier.ExtractClearsigned(s.signedF) + c.Assert(err, IsNil) + defer extractedF.Close() + + extracted, err := ioutil.ReadAll(extractedF) + c.Assert(err, IsNil) + + c.Assert(extracted, DeepEquals, s.cleartext) +} + +func (s *SignerSuite) TestClearSignNoPassphrase(c *C) { + s.signer.SetKeyRing("keyrings/aptly.pub", "keyrings/aptly.sec") + + s.testClearSign(c, "21DBB89C16DB3E6D") +} + +func (s *SignerSuite) TestClearSignPassphrase(c *C) { + s.signer.SetKeyRing("keyrings/aptly_passphrase.pub", "keyrings/aptly_passphrase.sec") + s.signer.SetPassphrase("verysecret", "") + + s.testClearSign(c, "F30E8CB9CDDE2AF8") +} + +func (s *SignerSuite) TestClearSignPassphraseFile(c *C) { + s.signer.SetKeyRing("keyrings/aptly_passphrase.pub", "keyrings/aptly_passphrase.sec") + s.signer.SetPassphrase("", s.passwordFile) + + s.testClearSign(c, "F30E8CB9CDDE2AF8") +} diff --git a/pgp/verify_test.go b/pgp/verify_test.go new file mode 100644 index 00000000..a5071534 --- /dev/null +++ b/pgp/verify_test.go @@ -0,0 +1,93 @@ +package pgp + +import ( + "bytes" + "io/ioutil" + "os" + + . "gopkg.in/check.v1" +) + +// Common set of tests shared by internal & external GnuPG implementations +type VerifierSuite struct { + verifier Verifier +} + +func (s *VerifierSuite) TestVerifyDetached(c *C) { + for _, test := range []struct { + textName, signatureName string + }{ + {"1.text", "1.signature"}, + {"2.text", "2.signature"}, + {"3.text", "3.signature"}, + } { + cleartext, err := os.Open(test.textName) + c.Assert(err, IsNil) + + signature, err := os.Open(test.signatureName) + c.Assert(err, IsNil) + + err = s.verifier.VerifyDetachedSignature(signature, cleartext, false) + c.Assert(err, IsNil) + + signature.Close() + cleartext.Close() + } +} + +func (s *VerifierSuite) TestVerifyClearsigned(c *C) { + for _, test := range []struct { + clearSignedName string + }{ + {"1.clearsigned"}, + } { + clearsigned, err := os.Open(test.clearSignedName) + c.Assert(err, IsNil) + + keyInfo, err := s.verifier.VerifyClearsigned(clearsigned, false) + c.Assert(err, IsNil) + c.Check(keyInfo.GoodKeys, DeepEquals, []Key{"8B48AD6246925553", "7638D0442B90D010"}) + c.Check(keyInfo.MissingKeys, DeepEquals, []Key(nil)) + + clearsigned.Close() + } +} + +func (s *VerifierSuite) TestExtractClearsigned(c *C) { + for _, test := range []struct { + clearSignedName, clearTextName string + }{ + {"1.clearsigned", "1.cleartext"}, + } { + clearsigned, err := os.Open(test.clearSignedName) + c.Assert(err, IsNil) + + cleartext, err := os.Open(test.clearTextName) + c.Assert(err, IsNil) + + is, err := s.verifier.IsClearSigned(clearsigned) + c.Assert(err, IsNil) + c.Check(is, Equals, true) + + clearsigned.Seek(0, 0) + + extractedF, err := s.verifier.ExtractClearsigned(clearsigned) + c.Assert(err, IsNil) + + expected, err := ioutil.ReadAll(cleartext) + c.Assert(err, IsNil) + + extracted, err := ioutil.ReadAll(extractedF) + c.Assert(err, IsNil) + + // normalize newlines + extracted = bytes.TrimRight(bytes.Replace(extracted, []byte("\r\n"), []byte("\n"), -1), "\n") + expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1) + + c.Check(extracted, DeepEquals, expected) + + extractedF.Close() + clearsigned.Close() + cleartext.Close() + } +}