diff --git a/signer/genericrsa/rsa.go b/signer/genericrsa/rsa.go index c69004acc..bad292fc2 100644 --- a/signer/genericrsa/rsa.go +++ b/signer/genericrsa/rsa.go @@ -55,9 +55,10 @@ const ( // Options contains options for creating and verifying PKCS15 signatures. type Options struct { - // Hash, if not zero, overrides the hash function passed to SignPSS. - // This is the only way to specify the hash function when using the - // crypto.Signer interface. + // Hash, if not zero, overrides the hash function passed to SignPSS. This is + // the only way to specify the hash function when using the crypto.Signer + // interface. crypto.Hash is "just" a uint, so the json.Marshal/Unmarshal + // calls in this package work on it, but it's a lil sketchy. Hash crypto.Hash } diff --git a/tools/autograph-monitor/monitor.go b/tools/autograph-monitor/monitor.go index b369b5d38..de4feb56a 100644 --- a/tools/autograph-monitor/monitor.go +++ b/tools/autograph-monitor/monitor.go @@ -44,11 +44,8 @@ type configuration struct { const inputdata string = "AUTOGRAPH MONITORING" -var ( - conf configuration -) - func main() { + conf := &configuration{} conf.url = os.Getenv("AUTOGRAPH_URL") if conf.url == "" { log.Fatal("AUTOGRAPH_URL must be set to the base url of the autograph service") @@ -90,9 +87,9 @@ func main() { if os.Getenv("LAMBDA_TASK_ROOT") != "" { // we are inside a lambda environment so run as lambda - lambda.Start(Handler) + lambda.Start(func() error { return Handler(conf, http.DefaultClient) }) } else { - err := Handler() + err := Handler(conf, http.DefaultClient) if err != nil { log.Fatalf("Unhandled exception from monitor: %s", err) } @@ -122,7 +119,7 @@ func LoadCertsToTruststore(pemStrings []string) (*x509.CertPool, []string, error // Handler is a wrapper around monitor() that performs garbage collection // before returning -func Handler() (err error) { +func Handler(conf *configuration, client *http.Client) (err error) { defer func() { // force gc run // https://bugzilla.mozilla.org/show_bug.cgi?id=1621133 @@ -130,11 +127,11 @@ func Handler() (err error) { runtime.GC() log.Println("Garbage collected in", time.Since(t1)) }() - return monitor() + return monitor(conf, client) } // monitor contacts the autograph service and verifies all monitoring signatures -func monitor() error { +func monitor(conf *configuration, client *http.Client) error { log.Println("Retrieving monitoring data from", conf.url) req, err := http.NewRequest("GET", conf.url+"__monitor__", nil) if err != nil { @@ -149,7 +146,7 @@ func monitor() error { req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", makeAuthHeader(req, "monitor", conf.monitoringKey)) - resp, err := http.DefaultClient.Do(req) + resp, err := client.Do(req) if err != nil { return fmt.Errorf("request to monitor endpoint failed: %w", err) } @@ -191,7 +188,7 @@ func monitor() error { err = verifyContentSignature(x5uClient, conf.notifier, conf.rootHashes, contentSignatureIgnoredLeafCertCNs, response, []byte(inputdata)) case xpi.Type: log.Printf("Verifying XPI signature from signer %q", response.SignerID) - err = verifyXPISignature(response.Signature) + err = verifyXPISignature(response.Signature, conf.truststore, conf.depTruststore) case apk2.Type: // we don't verify apk2 signatures because they can only be obtained on valid // APK files, which is too big to fit in the monitoring logic diff --git a/tools/autograph-monitor/monitor_test.go b/tools/autograph-monitor/monitor_test.go new file mode 100644 index 000000000..0efb5b646 --- /dev/null +++ b/tools/autograph-monitor/monitor_test.go @@ -0,0 +1,93 @@ +package main + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/mozilla-services/autograph/formats" +) + +func TestGoldenPath(t *testing.T) { + firstRsaKey := generateRSAKey(t) + secondRsaKey := generateRSAKey(t) + mux := http.NewServeMux() + mux.HandleFunc("GET /__monitor__", func(w http.ResponseWriter, r *http.Request) { + respBytes1, err := signatureRespForGenericaRSA("first", firstRsaKey) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + respBytes2, err := signatureRespForGenericaRSA("second", secondRsaKey) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusCreated) + w.Write(respBytes1) + w.Write(respBytes2) + }) + server := httptest.NewTLSServer(mux) + defer server.Close() + + conf := &configuration{ + url: server.URL + "/", + monitoringKey: "fakenotused", + truststore: x509.NewCertPool(), + } + err := Handler(conf, server.Client()) + if err != nil { + t.Errorf("handler error: %v", err) + } +} + +func generateRSAKey(t *testing.T) *rsa.PrivateKey { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("failed to generate RSA key: %v", err) + } + return key +} + +type rsaOption struct { + // crypto.Hash is just a uint, so the json.Marshal/Unmarshal calls work, + // but it's a lil sketchy. + crypto.Hash +} + +func (r *rsaOption) HashFunc() crypto.Hash { + return r.Hash +} + +func signatureRespForGenericaRSA(signerID string, key *rsa.PrivateKey) ([]byte, error) { + rsaOpt := &rsaOption{Hash: crypto.SHA256} + hash := rsaOpt.Hash.HashFunc().New() + _, err := hash.Write([]byte(inputdata)) + if err != nil { + return nil, err + } + + sig, err := key.Sign(rand.Reader, hash.Sum(nil), rsaOpt) + if err != nil { + return nil, err + } + marshalled, err := x509.MarshalPKIXPublicKey(key.Public()) + if err != nil { + return nil, err + } + resp := &formats.SignatureResponse{ + SignerID: signerID, + Type: "genericrsa", + Signature: base64.StdEncoding.EncodeToString(sig), + PublicKey: base64.StdEncoding.EncodeToString(marshalled), + Mode: "pkcs15", + SignerOpts: rsaOpt, + } + return json.Marshal(resp) +} diff --git a/tools/autograph-monitor/xpi.go b/tools/autograph-monitor/xpi.go index e8a69e834..2c7be3ad3 100644 --- a/tools/autograph-monitor/xpi.go +++ b/tools/autograph-monitor/xpi.go @@ -1,20 +1,21 @@ package main import ( + "crypto/x509" "log" "github.com/mozilla-services/autograph/signer/xpi" ) -func verifyXPISignature(sig string) error { +func verifyXPISignature(sig string, truststore, depTruststore *x509.CertPool) error { xpiSig, err := xpi.Unmarshal(sig, []byte(inputdata)) if err != nil { log.Fatal(err) } - err = xpiSig.VerifyWithChain(conf.truststore) - if err == nil || conf.depTruststore == nil { + err = xpiSig.VerifyWithChain(truststore) + if err == nil || depTruststore == nil { return err } log.Printf("Got error %s verifying XPI signature with rel truststore trying dep truststore", err) - return xpiSig.VerifyWithChain(conf.depTruststore) + return xpiSig.VerifyWithChain(depTruststore) }