From 4cc2669ae2d15cdba81a91e557f3906d6e7c9c07 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 15 Mar 2011 09:55:10 -0700 Subject: [PATCH] jsonsign: more work on tests --- lib/go/jsonsign/jsonsign_test.go | 142 +++++++++++++-------- lib/go/jsonsign/sign.go | 21 ++- lib/go/jsonsign/testdata/test-keyring.gpg | Bin 0 -> 1196 bytes lib/go/jsonsign/testdata/test-keyring2.gpg | Bin 0 -> 1202 bytes lib/go/jsonsign/testdata/test-secring.gpg | Bin 0 -> 2498 bytes lib/go/jsonsign/testdata/test-secring2.gpg | Bin 0 -> 2504 bytes 6 files changed, 110 insertions(+), 53 deletions(-) create mode 100644 lib/go/jsonsign/testdata/test-keyring.gpg create mode 100644 lib/go/jsonsign/testdata/test-keyring2.gpg create mode 100644 lib/go/jsonsign/testdata/test-secring.gpg create mode 100644 lib/go/jsonsign/testdata/test-secring2.gpg diff --git a/lib/go/jsonsign/jsonsign_test.go b/lib/go/jsonsign/jsonsign_test.go index 2540cd15e..86e132576 100644 --- a/lib/go/jsonsign/jsonsign_test.go +++ b/lib/go/jsonsign/jsonsign_test.go @@ -20,7 +20,9 @@ import ( "camli/blobref" . "camli/testing" + "fmt" "os" + "strings" "sync" "testing" ) @@ -29,9 +31,89 @@ var unsigned = `{"camliVersion": 1, "camliType": "foo" }` +var pubKey1 = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +mQENBEzgoVsBCAC/56aEJ9BNIGV9FVP+WzenTAkg12k86YqlwJVAB/VwdMlyXxvi +bCT1RVRfnYxscs14LLfcMWF3zMucw16mLlJCBSLvbZ0jn4h+/8vK5WuAdjw2YzLs +WtBcjWn3lV6tb4RJz5gtD/o1w8VWxwAnAVIWZntKAWmkcChCRgdUeWso76+plxE5 +aRYBJqdT1mctGqNEISd/WYPMgwnWXQsVi3x4z1dYu2tD9uO1dkAff12z1kyZQIBQ +rexKYRRRh9IKAayD4kgS0wdlULjBU98aeEaMz1ckuB46DX3lAYqmmTEL/Rl9cOI0 +Enpn/oOOfYFa5h0AFndZd1blMvruXfdAobjVABEBAAG0JUNhbWxpIFRlc3RlciA8 +Y2FtbGktdGVzdEBleGFtcGxlLmNvbT6JATgEEwECACIFAkzgoVsCGwMGCwkIBwMC +BhUIAgkKCwQWAgMBAh4BAheAAAoJECkxpnwm9avaHE0IAJ/pMZgiURl3kefrFMAV +7ei0XDfTekZOwDRcZWTVQ/A97phpzO8t78qLYbFeHuq3myNhrlVO9Gyp+2V904rN +dudoHLhpegf5TNeHGmAGHBxcooMPMp0JyIDnUBxtCNGxgWfbKpEDRsQAjkCc7sR0 +H+OegzlEf6JZGzEhV5ohOioTsC1DmJNoQsRz5Kes7sLoAzpQCbCv4yv+1o+mnzgW +9qPJXKxcScc0t2YTvcvpJ7LV8no1OP6vpYqB1A9Pzze6XFBlcXOUKbRKk0fEIV/u +pU3ph1fF7wlyRgA4A3iPwDC4BgVmHYkz9nYPn+7IcT/dDig5SWU+n7WZgGeyv75y +0Ue5AQ0ETOChWwEIALuHxKI+oSH+eeMSXhxcSUXnhp4cUeyvOV7oNPYcmsDclF0Y +7y8NrSPiEZod9vSTEDMq7hd3BG+feCBqjgR4qtmoXguJhWcnJqDBk5iAMuuAph9O +CC8QLACMJPhoxQ0UtDPKlpG4X8kLK1woHd716ulPl2KLjTgd6K4kCGj+CV5Ekn6u +IJj+3IPbYDOwk1l06ksimwQAY4dA1CXOTviH1bVqR6CzuzVPg4hcryWDva1rEO5c +LcOR8Wk/thANFLSNjqX8UgtGXhFZRWxKetFDQiX5f2BKoqTVYvD3pqt+zzyLNFAz +xhMc3cyFfqM8yQdzdEey/DIWtMoDqZCSVMJ63N8AEQEAAYkBHwQYAQIACQUCTOCh +WwIbDAAKCRApMaZ8JvWr2mHACACkco+fAfRK+gmprF2m8E0Bp1frwFH0g4RJVHXQ +BUDbg7OZbWumzD4Br28si6XDVMP6fLOeyD0EHYb6LhAHDkBLqx6e3kKG1mQ8fMIV +O4YMQfskYH2FJqlCtgMnM8N3oslPBTpZedNPSUq7HJh2pKr9GIDi1V+Hgc/qEigE +dj9f2zSSaKZdC4eL73GvlQOh+4XqgaMnMiKfI+/2WlRaJs1KOgKmIp5yHt0qY0ef +y+40BY/z9pMjyUvr/Wwp8KXArw0NAwzp8NUl5fNxRg9XWQWLn6hW8ydR20X3t2ym +iNSWzNQiTT6k7fumOABCoSZsow/AJxQSxqKOJBjgpKjIKCgY +=ru0J +-----END PGP PUBLIC KEY BLOCK-----` + +var pubKeyBlob1 = &blobref.TestBlob{pubKey1} + +var testFetcher = &TestFetcher{} + +func init() { + testFetcher.AddBlob(pubKeyBlob1) +} + +func TestSigningBadInput(t *testing.T) { + sr := newRequest() + + sr.UnsignedJson = "" + _, err := sr.Sign() + ExpectErrorContains(t, err, "json parse error", "empty input") + + sr.UnsignedJson = "{}" + _, err = sr.Sign() + ExpectErrorContains(t, err, "json lacks \"camliSigner\" key", "just braces") + + sr.UnsignedJson = `{"camliSigner": 123}` + _, err = sr.Sign() + ExpectErrorContains(t, err, "\"camliSigner\" key is malformed or unsupported", "camliSigner 123") + + sr.UnsignedJson = `{"camliSigner": ""}` + _, err = sr.Sign() + ExpectErrorContains(t, err, "\"camliSigner\" key is malformed or unsupported", "empty camliSigner") +} + +func newRequest() *SignRequest { + return &SignRequest{ + UnsignedJson: "", + Fetcher: testFetcher, + UseAgent: false, + ServerMode: true, + SecretKeyringPath: "./testdata/test-secring.gpg", + KeyringPath: "./testdata/test-keyring.gpg", + } +} + +func TestSigning(t *testing.T) { + sr := newRequest() + sr.UnsignedJson = fmt.Sprintf(`{"camliSigner": %q}`, pubKeyBlob1.BlobRef().String()) + got, err := sr.Sign() + AssertNil(t, err, "no error signing") + Expect(t, strings.Contains(got, `"camliSig":`), "got a camliSig") + + t.Logf("TODO: finish these tests; verify things round-trip, verify GPG external-vs-Go sign & verify round-trip, test signatures from wrong signer don't verify, etc.") +} + type TestFetcher struct { - l sync.Mutex - m map[string]*blobref.TestBlob + l sync.Mutex + m map[string]*blobref.TestBlob } func (tf *TestFetcher) AddBlob(b *blobref.TestBlob) { @@ -45,8 +127,8 @@ func (tf *TestFetcher) AddBlob(b *blobref.TestBlob) { func (tf *TestFetcher) Fetch(ref *blobref.BlobRef) (file blobref.ReadSeekCloser, size int64, err os.Error) { tf.l.Lock() - defer tf.l.Unlock() - if tf.m == nil { + defer tf.l.Unlock() + if tf.m == nil { err = os.ENOENT return } @@ -82,56 +164,14 @@ func (sr *strReader) Seek(offset int64, whence int) (ret int64, err os.Error) { } func (sr *strReader) Read(p []byte) (n int, err os.Error) { - remain := len(sr.s) - sr.pos - if remain <= 0 { + if sr.pos >= len(sr.s) { err = os.EOF return } - toCopy := len(p) - if remain < toCopy { - toCopy = remain + n = copy(p, sr.s[sr.pos:]) + if n == 0 { + err = os.EOF } - copy(p, sr.s[sr.pos:sr.pos+toCopy]) + sr.pos += n return } - -var testFetcher = &TestFetcher{} - -func TestSigningBadInput(t *testing.T) { - sr := newRequest() - - sr.UnsignedJson = "" - _, err := sr.Sign() - ExpectErrorContains(t, err, "json parse error", "empty input") - - sr.UnsignedJson = "{}" - _, err = sr.Sign() - ExpectErrorContains(t, err, "json lacks \"camliSigner\" key", "just braces") - - sr.UnsignedJson = `{"camliSigner": 123}` - _, err = sr.Sign() - ExpectErrorContains(t, err, "\"camliSigner\" key is malformed or unsupported", "camliSigner 123") - - sr.UnsignedJson = `{"camliSigner": ""}` - _, err = sr.Sign() - ExpectErrorContains(t, err, "\"camliSigner\" key is malformed or unsupported", "empty camliSigner") -} - -func newRequest() *SignRequest { - return &SignRequest{ - UnsignedJson: "", - Fetcher: testFetcher, - UseAgent: false, - ServerMode: true, - } -} - -func TestSigning(t *testing.T) { - sr := newRequest() - // TODO: finish test - got, err := sr.Sign() - if err != nil { - //t.Logf("Error signing: %v", err) - } - t.Logf("TODO; finish these tests; got: %s", got) -} diff --git a/lib/go/jsonsign/sign.go b/lib/go/jsonsign/sign.go index 0558a5e9a..81fa982c1 100644 --- a/lib/go/jsonsign/sign.go +++ b/lib/go/jsonsign/sign.go @@ -46,6 +46,23 @@ type SignRequest struct { // In server-mode, don't use any default (user) keys // TODO: formalize what this means? ServerMode bool + + SecretKeyringPath string + KeyringPath string +} + +func (sr *SignRequest) publicRingPath() string { + if sr.KeyringPath != "" { + return sr.KeyringPath + } + return *flagRing +} + +func (sr *SignRequest) secretRingPath() string { + if sr.SecretKeyringPath != "" { + return sr.SecretKeyringPath + } + return *flagSecretRing } func (sr *SignRequest) Sign() (signedJson string, err os.Error) { @@ -106,8 +123,8 @@ func (sr *SignRequest) Sign() (signedJson string, err os.Error) { if sr.ServerMode { args = append(args, "--no-default-keyring", - "--keyring", *flagRing, // TODO: needed for signing? - "--secret-keyring", *flagSecretRing) + "--keyring", sr.publicRingPath(), // TODO: needed for signing? + "--secret-keyring", sr.secretRingPath()) } args = append(args, "-") diff --git a/lib/go/jsonsign/testdata/test-keyring.gpg b/lib/go/jsonsign/testdata/test-keyring.gpg new file mode 100644 index 0000000000000000000000000000000000000000..3d20ba6837e26057e38b773a00e79af92f52acb6 GIT binary patch literal 1196 zcmV;d1XKH&0SyF9;GtUq2mrt5ri3TZO(11`6;u9OH>XSqAlGR;>58Sml|TpeaCFIX zUmN0VB=tp9U!9C>a?N-wx7;ydcg)M2!(OH?QbGkH?`@qUpNM|{%gW_zfOb4KV>0Yo z&|Hma_my6)Z-hzDm@N76B%w zQ`ToK8lyxZCx2Ok%!3KmT?-Y9e0a}SSi5UO_T#m7Kp%fyv(`+RK!8xK>`Gx2QHRnB z0jz`KND|WrWl*@mQ{NhRMvTu_B)A?r4SnSSil&({3;h{=aN;x)dT0KFj(vez<{bbQ zcUgB<`0stZf0!-kcTLK#c1`7!Y2Ll2I6$k#M1P`L8!;hQnjtzW6R<5qn3HHi#B=1QtnR|- z13FL%u&?7Q{??DCpEwrwqsd&XTuH|?w`LQ)%jqYw)$)2ZIR3Atihpcb`PKK$Z#^ znSf`qzrJ$ON3a3_1GxbW1We$eTLB0FyNAT0KA|E0dE*jZ99&67=Z2mfQS7fdUg$LT z9Gbw~lwBC_FAc3D;t`r1_VkkwGb-*EcLZ;rcpz$y1bC|1s9p<+g=Z%wpuv-vfHLcV zrXNlSFAyvMj3oGI#SIj+Gs>2cxL?T&D_kfY-u3F~PnTkgjW`|Xt|SO({s~@0l76lr znEu>@+h8-WlUa1?OCp;D0Aq(h)FsYN_=nZCYDb{6yERXPh+MBFgT1Y55bj(p!;$f6 zKeiAJ6ts?EN3#4f7PQI(sgROX!g}1_01*KI0f_-01Q-DV00{*GOyHqg0vikf3JDM?F{XSb z^{d)pzz6`Oa*v+@^h){(sjOY5@J#`ySL?u0^n-*+RCUk=K-+_}nQd#P%sv6HZ!C+Y z!&Jlie6ya&Jp>(w`YsR$4nRw*9-iJphSp>}e8Lqwh73XbBw&4oCaFTU11B@XccRHp z1v*)I(@#lCyBwHyq^kWGfa29(hk?)P5-0?AKVRE4l4zz~3x|vEaj%sFq5FmEfukof zBA+Ag_F7b0Ce2DZ0;VFKavt3(V@IFM?lc9D^Y)V?$xG|~Y$@=iz^@Gr0}Sc#)g|Tg zaYheUSp|!qs8;hQQQJlLw``_})RxTDB27M|?fa%U079WAY@-jrClnIKqK+gO;H0R? KC@2`P0ssRNTnzyL literal 0 HcmV?d00001 diff --git a/lib/go/jsonsign/testdata/test-keyring2.gpg b/lib/go/jsonsign/testdata/test-keyring2.gpg new file mode 100644 index 0000000000000000000000000000000000000000..ded7d5151dfbfb7a3c78487e9b4a5173fac49fed GIT binary patch literal 1202 zcmV;j1Wo&y0SyF9`qozg2msMS7aR1l5OrP4g-&1Xz@s_KW-h}1%ZWHtsk5HT7C9J= zJVDUcaIqA)(G1;=L51Sr)T*IMhcwF(O+r&uZ33S;)KR^4iM7j7g}=?S&Gpr+@cBql zl#+2RUB=OhDo>Stdhc^m!Rnf;jcI^@Oc2JvR!)VsAXI;N$~7(}RP%Rpq@^{V{G0Hc zy}~TqhRR*{7o00)^d@qPM1o|Lw7@A3mni?7O9kdi7cBdFal4oJrYeQ*fM18%=oAev zbY=|%Wn71F-zNcFPGaPcVOc5Io`q@Ly;c*}JXDaI+|aH#q7cWjX$IL=X5O+w!Yzg& zU_WwF;tU8_Gpo}hdKmx_0RRECD??#zY-u1=Wpi|Ob7gWMRCjM6JY!*PY-uiZWpi{u zWq4t2aBO8RV{dIfi2*nS69EDMA_W3W`qozh8v_Ol2?z%R0tOWb0tpHW1Qr4V0RkQY z0vCV)3JDN}9tg5a>{_*RjtBswiH|9$ign%e)0XUK=^QDtpERG8l4wQ5bFkd}-cnn@ zgxCq)M8ynbJI*XHq)#R%pL5r#Kk?x4UK`acBpNS(@n)zW^O>|mJRA<<5UG{>2;XE* zq_^y}wzlzh?lxPPOV9hBuCrwu>iyDSO^#w$v7BbXrbHI9?G{>|%q-nOE34m>3uM7> zTc)UBRKS4gHLg!(2H64)go#Dz;hd z7jz2b0SB^#V3r74Hxf14Ap^B9F?)^Kv56jT--3W2E`*r8pdj78q#FbKN2H5DI@jrZLXI0YwWmvvF_S zwes!(!{^F@&X(MSJ(0>3C0RRDs0Urby0RjLC1p-X^)>i@>3;+rV z5QQEHvP+oIwgYi}0fSga0NhDa zjGCmpgz|BN_Tm>2Xc$-~*M(`)cXe)4hjL?Aa6IH)JN5{t6mAA7yftQgCj@`19!dYd zL^kq|^>cZ#I?DC#!o%*fx$SJsJ`U-)+dg?+h?ueQ1-ouhw;JNKZQf!@LMthqMh>;+ znEcL!X)l&_Gz_^w7WuONVLi7-ODc;i4w@~>wjoaBdj5i7m2zJ)bza*Tzxq3eGK!g= zR&xuFbJi+{swXDhQ47fP&%|<%B~3lQ0BNO_TU(hf4uqSnS}JzV8(jh4Nw*0P9l#jH Q_82C$#xxPx# literal 0 HcmV?d00001 diff --git a/lib/go/jsonsign/testdata/test-secring.gpg b/lib/go/jsonsign/testdata/test-secring.gpg new file mode 100644 index 0000000000000000000000000000000000000000..bca3ad0392b59249597bc1ec8a93a5d9f6141758 GIT binary patch literal 2498 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 MsK_WN7_b5W05^l1$p8QV literal 0 HcmV?d00001 diff --git a/lib/go/jsonsign/testdata/test-secring2.gpg b/lib/go/jsonsign/testdata/test-secring2.gpg new file mode 100644 index 0000000000000000000000000000000000000000..f4b7ed22286696c1f8ee02530e882fc2252bbdaf GIT binary patch literal 2504 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 S7{&G&Cbh;iA)nZ=0ssKko~)Aq literal 0 HcmV?d00001