github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/jsonsign/sign.go (about) 1 /* 2 Copyright 2011 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package jsonsign 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "io" 25 "os" 26 "strings" 27 "sync" 28 "time" 29 "unicode" 30 31 "camlistore.org/pkg/blob" 32 "camlistore.org/pkg/osutil" 33 "camlistore.org/third_party/code.google.com/p/go.crypto/openpgp" 34 ) 35 36 type EntityFetcher interface { 37 FetchEntity(keyId string) (*openpgp.Entity, error) 38 } 39 40 type FileEntityFetcher struct { 41 File string 42 } 43 44 func FlagEntityFetcher() *FileEntityFetcher { 45 return &FileEntityFetcher{File: osutil.IdentitySecretRing()} 46 } 47 48 type CachingEntityFetcher struct { 49 Fetcher EntityFetcher 50 51 lk sync.Mutex 52 m map[string]*openpgp.Entity 53 } 54 55 func (ce *CachingEntityFetcher) FetchEntity(keyId string) (*openpgp.Entity, error) { 56 ce.lk.Lock() 57 if ce.m != nil { 58 e := ce.m[keyId] 59 if e != nil { 60 ce.lk.Unlock() 61 return e, nil 62 } 63 } 64 ce.lk.Unlock() 65 66 e, err := ce.Fetcher.FetchEntity(keyId) 67 if err == nil { 68 ce.lk.Lock() 69 defer ce.lk.Unlock() 70 if ce.m == nil { 71 ce.m = make(map[string]*openpgp.Entity) 72 } 73 ce.m[keyId] = e 74 } 75 76 return e, err 77 } 78 79 func (fe *FileEntityFetcher) FetchEntity(keyId string) (*openpgp.Entity, error) { 80 f, err := os.Open(fe.File) 81 if err != nil { 82 return nil, fmt.Errorf("jsonsign: FetchEntity: %v", err) 83 } 84 defer f.Close() 85 el, err := openpgp.ReadKeyRing(f) 86 if err != nil { 87 return nil, fmt.Errorf("jsonsign: openpgp.ReadKeyRing of %q: %v", fe.File, err) 88 } 89 for _, e := range el { 90 pubk := &e.PrivateKey.PublicKey 91 if pubk.KeyIdString() != keyId { 92 continue 93 } 94 if e.PrivateKey.Encrypted { 95 if err := fe.decryptEntity(e); err == nil { 96 return e, nil 97 } else { 98 return nil, err 99 } 100 } 101 return e, nil 102 } 103 return nil, fmt.Errorf("jsonsign: entity for keyid %q not found in %q", keyId, fe.File) 104 } 105 106 type SignRequest struct { 107 UnsignedJSON string 108 Fetcher interface{} // blobref.Fetcher or blob.StreamingFetcher 109 ServerMode bool // if true, can't use pinentry or gpg-agent, etc. 110 111 // Optional signature time. If zero, time.Now() is used. 112 SignatureTime time.Time 113 114 // Optional function to return an entity (including decrypting 115 // the PrivateKey, if necessary) 116 EntityFetcher EntityFetcher 117 118 // SecretKeyringPath is only used if EntityFetcher is nil, 119 // in which case SecretKeyringPath is used if non-empty. 120 // As a final resort, the flag value (defaulting to 121 // ~/.gnupg/secring.gpg) is used. 122 SecretKeyringPath string 123 } 124 125 func (sr *SignRequest) secretRingPath() string { 126 if sr.SecretKeyringPath != "" { 127 return sr.SecretKeyringPath 128 } 129 return osutil.IdentitySecretRing() 130 } 131 132 func (sr *SignRequest) Sign() (signedJSON string, err error) { 133 trimmedJSON := strings.TrimRightFunc(sr.UnsignedJSON, unicode.IsSpace) 134 135 // TODO: make sure these return different things 136 inputfail := func(msg string) (string, error) { 137 return "", errors.New(msg) 138 } 139 execfail := func(msg string) (string, error) { 140 return "", errors.New(msg) 141 } 142 143 jmap := make(map[string]interface{}) 144 if err := json.Unmarshal([]byte(trimmedJSON), &jmap); err != nil { 145 return inputfail("json parse error") 146 } 147 148 camliSigner, hasSigner := jmap["camliSigner"] 149 if !hasSigner { 150 return inputfail("json lacks \"camliSigner\" key with public key blobref") 151 } 152 153 camliSignerStr, _ := camliSigner.(string) 154 signerBlob, ok := blob.Parse(camliSignerStr) 155 if !ok { 156 return inputfail("json \"camliSigner\" key is malformed or unsupported") 157 } 158 159 var pubkeyReader io.ReadCloser 160 switch fetcher := sr.Fetcher.(type) { 161 case blob.SeekFetcher: 162 pubkeyReader, _, err = fetcher.Fetch(signerBlob) 163 case blob.StreamingFetcher: 164 pubkeyReader, _, err = fetcher.FetchStreaming(signerBlob) 165 default: 166 panic(fmt.Sprintf("jsonsign: bogus SignRequest.Fetcher of type %T", sr.Fetcher)) 167 } 168 if err != nil { 169 // TODO: not really either an inputfail or an execfail.. but going 170 // with exec for now. 171 return execfail(fmt.Sprintf("failed to find public key %s: %v", signerBlob.String(), err)) 172 } 173 174 pubk, err := openArmoredPublicKeyFile(pubkeyReader) 175 pubkeyReader.Close() 176 if err != nil { 177 return execfail(fmt.Sprintf("failed to parse public key from blobref %s: %v", signerBlob.String(), err)) 178 } 179 180 // This check should be redundant if the above JSON parse succeeded, but 181 // for explicitness... 182 if len(trimmedJSON) == 0 || trimmedJSON[len(trimmedJSON)-1] != '}' { 183 return inputfail("json parameter lacks trailing '}'") 184 } 185 trimmedJSON = trimmedJSON[0 : len(trimmedJSON)-1] 186 187 // sign it 188 entityFetcher := sr.EntityFetcher 189 if entityFetcher == nil { 190 file := sr.secretRingPath() 191 if file == "" { 192 return "", errors.New("jsonsign: no EntityFetcher, SecretKeyringPath, or secret-keyring flag provided") 193 } 194 secring, err := os.Open(sr.secretRingPath()) 195 if err != nil { 196 return "", fmt.Errorf("jsonsign: failed to open secret ring file %q: %v", sr.secretRingPath(), err) 197 } 198 secring.Close() // just opened to see if it's readable 199 entityFetcher = &FileEntityFetcher{File: file} 200 } 201 signer, err := entityFetcher.FetchEntity(pubk.KeyIdString()) 202 if err != nil { 203 return "", err 204 } 205 206 var buf bytes.Buffer 207 err = openpgp.ArmoredDetachSignAt(&buf, signer, sr.SignatureTime, strings.NewReader(trimmedJSON)) 208 if err != nil { 209 return "", err 210 } 211 212 output := buf.String() 213 214 index1 := strings.Index(output, "\n\n") 215 index2 := strings.Index(output, "\n-----") 216 if index1 == -1 || index2 == -1 { 217 return execfail("Failed to parse signature from gpg.") 218 } 219 inner := output[index1+2 : index2] 220 signature := strings.Replace(inner, "\n", "", -1) 221 222 return fmt.Sprintf("%s,\"camliSig\":\"%s\"}\n", trimmedJSON, signature), nil 223 }