From 79ba0a1ff23630649958999c5e123f0728ee2296 Mon Sep 17 00:00:00 2001 From: cyrill-k Date: Fri, 15 Mar 2024 16:01:33 +0100 Subject: [PATCH] Cyrill Map Server Fixes and Improvements (#57) * show mapserver public key when starting and add helper functions * adjust parsing rules to CT log standard (addresses issue #50) * log updater progress * fix null pointer exception in empty tree * fix null pointer exception if initializing tree with policy certificate * make HTTP API port configurable * fix issue of responder using wrong SMT root * sign tree head after update * allow log fetchers to run multiple times (fix closed results channel bug) * extend policy attributes with disallowed and excluded domains * fix closing bad idle connection bug * remove cooloff checking at PCA (should be done at client) * add/clean up logging * add version information * formatting * Add warning if fetching speed is too low * Fix comment. --------- Co-authored-by: Juan A. Garcia Pardo --- cmd/mapserver/main.go | 31 +++++++- pkg/common/crypto/crypto.go | 9 +++ pkg/common/policies.go | 106 ++++++++++++++++++++++++- pkg/db/mysql/init.go | 6 ++ pkg/mapserver/config/config.go | 1 + pkg/mapserver/logfetcher/logfetcher.go | 7 +- pkg/mapserver/mapserver.go | 44 ++++++++-- pkg/mapserver/mapserver_test.go | 7 +- pkg/mapserver/responder/responder.go | 33 +++++--- pkg/mapserver/updater/updater.go | 13 +++ pkg/pca/pca.go | 6 +- pkg/util/pem.go | 17 ++++ tests/integration/mapserver/main.go | 12 +-- 13 files changed, 259 insertions(+), 33 deletions(-) diff --git a/cmd/mapserver/main.go b/cmd/mapserver/main.go index 6e3bc974..80eb76da 100644 --- a/cmd/mapserver/main.go +++ b/cmd/mapserver/main.go @@ -3,6 +3,7 @@ package main import ( "bytes" "context" + "crypto/rsa" "flag" "fmt" "os" @@ -18,6 +19,8 @@ import ( "github.com/netsec-ethz/fpki/pkg/util" ) +const VERSION = "0.1.0" + const waitForExitBeforePanicTime = 10 * time.Second func main() { @@ -36,12 +39,20 @@ func mainFunc() int { } flag.CommandLine.Usage = flag.Usage + var showVersion bool + flag.BoolVar(&showVersion, "version", false, "Print map server version") + flag.BoolVar(&showVersion, "v", false, "Print map server version") updateVar := flag.Bool("updateNow", false, "Immediately trigger an update cycle") createSampleConfig := flag.Bool("createSampleConfig", false, "Create configuration file specified by positional argument") insertPolicyVar := flag.String("policyFile", "", "policy certificate file to be ingested into the mapserver") flag.Parse() + if showVersion { + fmt.Printf("FP-PKI Map Server %s\n", VERSION) + return 0 + } + // We need the configuration file as the first positional argument. if flag.NArg() != 1 { flag.Usage() @@ -109,7 +120,9 @@ func insertPolicyFromFile(policyFile string) error { if err != nil { return err } - if bytes.Equal(root[:], newRoot[:]) { + if root == nil { + fmt.Printf("MHT root value initially set to %v\n", newRoot) + } else if bytes.Equal(root[:], newRoot[:]) { fmt.Printf("MHT root value was not updated (%v)\n", newRoot) } else { fmt.Printf("MHT root value updated from %v to %v\n", root, newRoot) @@ -129,6 +142,7 @@ func writeSampleConfig() error { CTLogServerURLs: []string{"https://ct.googleapis.com/logs/xenon2023/"}, CertificatePemFile: "tests/testdata/servercert.pem", PrivateKeyPemFile: "tests/testdata/serverkey.pem", + HttpAPIPort: 8443, UpdateAt: util.NewTimeOfDay(3, 00, 00, 00), UpdateTimer: util.DurationWrap{ @@ -170,7 +184,15 @@ func runWithConfig( if err != nil { return err } - fmt.Printf("Running map server with root: %v\n", root) + base64PublicKey, err := util.RSAPublicToDERBase64(server.Cert.PublicKey.(*rsa.PublicKey)) + if err != nil { + return fmt.Errorf("error converting public key to DER base64: %w", err) + } + if root == nil { + fmt.Printf("Running empy map server (%s) with public key: %s\n", VERSION, base64PublicKey) + } else { + fmt.Printf("Running map server (%s) with root: %x and public key: %s\n", VERSION, *root, base64PublicKey) + } // Should update now? if updateNow { @@ -183,10 +205,13 @@ func runWithConfig( // Set update cycle timer. util.RunWhen(ctx, conf.UpdateAt.NextTimeOfDay(), conf.UpdateTimer.Duration, func(ctx context.Context) { - err := server.PruneAndUpdate(ctx) + updatePossible, err := server.PruneAndUpdateIfPossible(ctx) if err != nil { fmt.Printf("ERROR: update returned %s\n", err) } + if !updatePossible { + fmt.Printf("WARNING: Unable to schedule update due to currently running update (CT log fetching and map server ingestion speed may be too low) at %s\n", time.Now().UTC().Format(time.RFC3339)) + } }) // Listen in responder. diff --git a/pkg/common/crypto/crypto.go b/pkg/common/crypto/crypto.go index 4fb1d007..9ec172c6 100644 --- a/pkg/common/crypto/crypto.go +++ b/pkg/common/crypto/crypto.go @@ -53,6 +53,15 @@ func SignBytes(b []byte, key *rsa.PrivateKey) ([]byte, error) { return signature, nil } +func VerifySignedBytes(b []byte, signature []byte, key *rsa.PublicKey) error { + hashOutput := sha256.Sum256(b) + err := rsa.VerifyPKCS1v15(key, crypto.SHA256, hashOutput[:], signature) + if err != nil { + return fmt.Errorf("VerifySignature | VerifyPKCS1V15 | %w", err) + } + return nil +} + // SignAsOwner generates a signature using the owner's key, and fills the owner signature in // the policy certificate signing request. // diff --git a/pkg/common/policies.go b/pkg/common/policies.go index e8f63b7c..4d64f0b2 100644 --- a/pkg/common/policies.go +++ b/pkg/common/policies.go @@ -2,6 +2,9 @@ package common import ( "bytes" + "fmt" + "slices" + "strings" "time" ) @@ -87,8 +90,25 @@ func (o PolicyCertificate) Raw() ([]byte, error) { // PolicyAttributes is a domain policy that specifies what is or not acceptable for a domain. type PolicyAttributes struct { - TrustedCA []string `json:",omitempty"` + // List of CA subject names allowed to issue certificates for this domain and subdomains. The + // string representation is according to the golang x509 library + // golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 + AllowedCAs []string `json:",omitempty"` + + // The following three attributes specify which subdomains are allowed and which are excluded + // (i.e., policies do not apply to these subdomains). Only one of these attributes is allowed to + // be set to the wildcard value "*" and covers all domains that are not covered by other + // attributes. No subdomain name can be covered by more than one attribute (including the + // wildcard value). Only single labels without "." can be specified. + + // This attribute lists subdomains that are allowed AllowedSubdomains []string `json:",omitempty"` + + // This attribute lists subdomains that are not allowed + DisallowedSubdomains []string `json:",omitempty"` + + // This attribute lists sudomains for which no policy applies + ExcludedSubdomains []string `json:",omitempty"` } type PolicyCertificateRevocationFields struct { @@ -110,6 +130,84 @@ func (o PolicyCertificateRevocation) Raw() ([]byte, error) { return rawTemplate(o) } +type PolicyAttributeDomainValidityResult int + +const ( + PolicyAttributeDomainAllowed PolicyAttributeDomainValidityResult = iota + PolicyAttributeDomainDisallowed = iota + PolicyAttributeDomainExcluded = iota + PolicyAttributeDomainNotApplicable = iota +) + +func (a PolicyAttributes) ValidateAttributes() error { + caMap := map[string]struct{}{} + for _, caName := range a.AllowedCAs { + caMap[caName] = struct{}{} + } + if len(caMap) < len(a.AllowedCAs) { + return fmt.Errorf("Allowed CA attribute contains %d duplicate CAs", len(caMap)-len(a.AllowedCAs)) + } + + // TODO: could also check CA subject name formatting + + labelMap := map[string]struct{}{} + for _, label := range a.AllowedSubdomains { + labelMap[label] = struct{}{} + } + for _, label := range a.DisallowedSubdomains { + labelMap[label] = struct{}{} + } + for _, label := range a.ExcludedSubdomains { + labelMap[label] = struct{}{} + } + nLabels := len(a.AllowedSubdomains) + len(a.DisallowedSubdomains) + len(a.ExcludedSubdomains) + if len(labelMap) < nLabels { + return fmt.Errorf("Subdomain attributes contain %d duplicate labels", nLabels-len(labelMap)) + } + return nil +} + +func (a PolicyAttributes) CheckDomainValidity(policyAttributeDomain, domain string) PolicyAttributeDomainValidityResult { + // remove trailing dot if present (example.com. -> example.com) + policyAttributeDomain, _ = strings.CutSuffix(policyAttributeDomain, ".") + domain, _ = strings.CutSuffix(domain, ".") + + // get relative domain path compared to the policy attribute's domain (www.test.example.com -> www.test + targetSubdomain, ok := strings.CutSuffix(domain, "."+policyAttributeDomain) + if !ok { + return PolicyAttributeDomainNotApplicable + } + + // get the relevant subdomain label on which the domain validity is evaluated on (www.test.example.com -> test) + targetSubdomainLabels := strings.Split(targetSubdomain, ".") + targetSubdomainLabel := targetSubdomainLabels[len(targetSubdomainLabels)-1] + + // check for exact match + if slices.Contains(a.AllowedSubdomains, targetSubdomainLabel) { + return PolicyAttributeDomainAllowed + } + if slices.Contains(a.DisallowedSubdomains, targetSubdomainLabel) { + return PolicyAttributeDomainDisallowed + } + if slices.Contains(a.ExcludedSubdomains, targetSubdomainLabel) { + return PolicyAttributeDomainExcluded + } + + // check for wildcards + if slices.Contains(a.AllowedSubdomains, "*") { + return PolicyAttributeDomainAllowed + } + if slices.Contains(a.DisallowedSubdomains, "*") { + return PolicyAttributeDomainDisallowed + } + if slices.Contains(a.ExcludedSubdomains, "*") { + return PolicyAttributeDomainExcluded + } + + // default is to allow any subdomain + return PolicyAttributeDomainAllowed +} + func NewPolicyCertificateFields( version int, serialNumber int, @@ -237,8 +335,10 @@ func (c PolicyCertificate) Equal(x PolicyCertificate) bool { func (s PolicyAttributes) Equal(o PolicyAttributes) bool { return true && - equalStringSlices(s.TrustedCA, o.TrustedCA) && - equalStringSlices(s.AllowedSubdomains, o.AllowedSubdomains) + equalStringSlices(s.AllowedCAs, o.AllowedCAs) && + equalStringSlices(s.AllowedSubdomains, o.AllowedSubdomains) && + equalStringSlices(s.DisallowedSubdomains, o.DisallowedSubdomains) && + equalStringSlices(s.ExcludedSubdomains, o.ExcludedSubdomains) } func NewPolicyCertificateRevocationFields( diff --git a/pkg/db/mysql/init.go b/pkg/db/mysql/init.go index c032c22b..dab7673e 100644 --- a/pkg/db/mysql/init.go +++ b/pkg/db/mysql/init.go @@ -4,6 +4,7 @@ import ( "database/sql" "fmt" "net/url" + "time" _ "github.com/go-sql-driver/mysql" @@ -33,6 +34,11 @@ func Connect(config *db.Configuration) (db.Conn, error) { maxConnections := 8 db.SetMaxOpenConns(maxConnections) + // Set the maximum idle connection time to a lower value than the mysql wait_timeout (8h) to + // ensure that idle connections that are closed by the mysql DB are not reused + connMaxIdleTime := 1 * time.Hour + db.SetConnMaxIdleTime(connMaxIdleTime) + // check schema if config.CheckSchema { if err := checkSchema(db); err != nil { diff --git a/pkg/mapserver/config/config.go b/pkg/mapserver/config/config.go index 2c4674d8..75d7b0b7 100644 --- a/pkg/mapserver/config/config.go +++ b/pkg/mapserver/config/config.go @@ -13,6 +13,7 @@ type Config struct { DBConfig *db.Configuration CertificatePemFile string // A X509 pem certificate PrivateKeyPemFile string // A RSA pem key + HttpAPIPort int UpdateAt util.TimeOfDayWrap UpdateTimer util.DurationWrap diff --git a/pkg/mapserver/logfetcher/logfetcher.go b/pkg/mapserver/logfetcher/logfetcher.go index db3478dd..bb9133ae 100644 --- a/pkg/mapserver/logfetcher/logfetcher.go +++ b/pkg/mapserver/logfetcher/logfetcher.go @@ -99,7 +99,7 @@ func NewLogFetcher(url string) (*LogFetcher, error) { serverBatchSize: defaultServerBatchSize, processBatchSize: defaultProcessBatchSize, ctClient: ctClient, - chanResults: make(chan *result, preloadCount), + chanResults: nil, }, nil } @@ -123,6 +123,7 @@ func (f LogFetcher) GetCurrentState(ctx context.Context) (State, error) { // StartFetching will start fetching certificates in the background, so that there is // at most two batches ready to be immediately read by NextBatch. func (f *LogFetcher) StartFetching(start, end int64) { + f.chanResults = make(chan *result, preloadCount) f.start = start f.end = end go f.fetch() @@ -233,7 +234,9 @@ func (f *LogFetcher) fetch() { } // Certificate. cert, err := ctx509.ParseCertificate(raw.Cert.Data) - if err != nil { + // Accept the same certificates as CT logs, i.e., don't be too restrictive in terms of + // which certificates to reject (i.e., allow for non-fatal parsing/validation errors) + if ctx509.IsFatal(err) { f.chanResults <- &result{ err: err, } diff --git a/pkg/mapserver/mapserver.go b/pkg/mapserver/mapserver.go index 49d44d79..86975061 100644 --- a/pkg/mapserver/mapserver.go +++ b/pkg/mapserver/mapserver.go @@ -23,8 +23,6 @@ import ( "github.com/netsec-ethz/fpki/pkg/util" ) -const APIPort = 8443 // TODO: should be a config parameter - type PayloadReturnType int const ( @@ -43,6 +41,7 @@ type MapServer struct { TLS *tls.Certificate ReadTimeout time.Duration WriteTimeout time.Duration + HttpAPIPort int apiStopServerChan chan struct{} updateChan chan context.Context @@ -112,6 +111,7 @@ func NewMapServer(ctx context.Context, conf *config.Config) (*MapServer, error) TLS: &tlsCert, ReadTimeout: 5 * time.Second, WriteTimeout: 5 * time.Second, + HttpAPIPort: conf.HttpAPIPort, apiStopServerChan: make(chan struct{}, 1), updateChan: make(chan context.Context), @@ -124,7 +124,14 @@ func NewMapServer(ctx context.Context, conf *config.Config) (*MapServer, error) for { select { case c := <-s.updateChan: + // TODO: ensure that the Mapserver is never in an inconsistent state. Currently, if + // the new SMT is applied but the responder does not yet use the updated SMT root + // value, queries will fail s.pruneAndUpdate(c) + err := s.Responder.ReloadRootAndSignTreeHead(c, s.Key) + if err != nil { + s.updateErrChan <- err + } case <-ctx.Done(): // Requested to exit. close(s.updateChan) @@ -156,7 +163,7 @@ func (s *MapServer) listen(ctx context.Context, useTLS bool) error { http.HandleFunc("/getpolicypayloads", func(w http.ResponseWriter, r *http.Request) { s.apiGetPayloads(w, r, Policies) }) server := &http.Server{ - Addr: fmt.Sprintf(":%d", APIPort), + Addr: fmt.Sprintf(":%d", s.HttpAPIPort), TLSConfig: &tls.Config{ Certificates: []tls.Certificate{*s.TLS}, }, @@ -172,7 +179,7 @@ func (s *MapServer) listen(ctx context.Context, useTLS bool) error { server.Shutdown(ctx) }() // This call blocks - fmt.Printf("Listening on %d\n", APIPort) + fmt.Printf("Listening on %d\n", s.HttpAPIPort) if useTLS { chanErr <- server.ListenAndServeTLS("", "") } else { @@ -195,6 +202,21 @@ func (s *MapServer) Shutdown(ctx context.Context) { s.apiStopServerChan <- struct{}{} } +// PruneAndUpdateIfPossible tries to trigger an update if no update is currently running. +// If an ongoing update is still in process, it returns false. Returns true if a new update was +// triggered. +func (s *MapServer) PruneAndUpdateIfPossible(ctx context.Context) (bool, error) { + select { + // Signal we want an update. + case s.updateChan <- ctx: + // Wait for the answer (in form of an error). + err := <-s.updateErrChan + return true, err + default: + return false, nil + } +} + // PruneAndUpdate triggers an update. If an ongoing update is still in process, it blocks. func (s *MapServer) PruneAndUpdate(ctx context.Context) error { // Signal we want an update. @@ -340,10 +362,20 @@ func (s *MapServer) updateCerts(ctx context.Context) error { defer s.Updater.StopFetching() // Main update loop. + start := time.Now() for s.Updater.NextBatch(ctx) { - n, err := s.Updater.UpdateNextBatch(ctx) + // print progress information + logUrl, currentIndex, maxIndex, err := s.Updater.GetProgress() + if err != nil { + return fmt.Errorf("retrieve progress: %s", err) + } + fmt.Printf("Running updater for log %s in range (%d, %d)\n", logUrl, currentIndex, maxIndex) - fmt.Printf("updated %5d certs batch at %s\n", n, getTime()) + fetchDuration := time.Now().Sub(start) + start = time.Now() + + n, err := s.Updater.UpdateNextBatch(ctx) + fmt.Printf("Fetched %d certs in %.2f seconds at %s\n", n, fetchDuration.Seconds(), getTime()) if err != nil { // We stop the loop here, as probably requires manual inspection of the logs, etc. return fmt.Errorf("updating next batch of x509 certificates: %w", err) diff --git a/pkg/mapserver/mapserver_test.go b/pkg/mapserver/mapserver_test.go index ae1d71ac..bab93ffc 100644 --- a/pkg/mapserver/mapserver_test.go +++ b/pkg/mapserver/mapserver_test.go @@ -54,6 +54,7 @@ func TestServer(t *testing.T) { DBConfig: dbConf, CertificatePemFile: "../../tests/testdata/servercert.pem", PrivateKeyPemFile: "../../tests/testdata/serverkey.pem", + HttpAPIPort: 8443, } // Run mapserver: @@ -97,7 +98,7 @@ func TestServer(t *testing.T) { defer wgClients.Done() resp, err := client.Get(fmt.Sprintf("https://localhost:%d/getproof?domain=%s", - mapserver.APIPort, domains[rand.Intn(len(domains))])) + server.HttpAPIPort, domains[rand.Intn(len(domains))])) require.NoError(t, err) body, err := io.ReadAll(resp.Body) require.NoError(t, err) @@ -186,7 +187,7 @@ func benchmarkAPIGetProof(b *testing.B, numDifferentDomains int) { defer wg.Done() resp, err := client.Get(fmt.Sprintf("https://localhost:%d/getproof?domain=%s", - mapserver.APIPort, domains[rand.Intn(len(domains))])) + server.HttpAPIPort, domains[rand.Intn(len(domains))])) b.StopTimer() require.NoError(b, err) body, err := io.ReadAll(resp.Body) @@ -267,7 +268,7 @@ func benchmarkAPIGetPayloads(b *testing.B, numDifferentDomains int) { ID := hex.EncodeToString(certIDs[rand.Intn(len(certIDs))][:]) resp, err := client.Get(fmt.Sprintf("https://localhost:%d/getcertpayloads?ids=%s", - mapserver.APIPort, ID)) + server.HttpAPIPort, ID)) b.StopTimer() require.NoError(b, err) body, err := io.ReadAll(resp.Body) diff --git a/pkg/mapserver/responder/responder.go b/pkg/mapserver/responder/responder.go index 61c57237..b07d9d53 100644 --- a/pkg/mapserver/responder/responder.go +++ b/pkg/mapserver/responder/responder.go @@ -25,26 +25,41 @@ func NewMapResponder( privateKey *rsa.PrivateKey, ) (*MapResponder, error) { + r := &MapResponder{ + conn: conn, + smt: nil, + } + err := r.ReloadRootAndSignTreeHead(ctx, privateKey) + if err != nil { + return nil, err + } + + return r, nil +} + +func (r *MapResponder) ReloadRootAndSignTreeHead( + ctx context.Context, + privateKey *rsa.PrivateKey, +) error { // Load root. var root []byte - if rootID, err := conn.LoadRoot(ctx); err != nil { - return nil, err + if rootID, err := r.conn.LoadRoot(ctx); err != nil { + return err } else if rootID != nil { root = rootID[:] } // Build the Sparse Merkle Tree (SMT). - smt, err := trie.NewTrie(root, common.SHA256Hash, conn) + smt, err := trie.NewTrie(root, common.SHA256Hash, r.conn) if err != nil { - return nil, fmt.Errorf("error loading SMT: %w", err) + return fmt.Errorf("error loading SMT: %w", err) } - r := &MapResponder{ - conn: conn, - smt: smt, - } + // Use the new SMT + r.smt = smt - return r, r.signTreeHead(privateKey) + // sign the SMT root + return r.signTreeHead(privateKey) } func (r *MapResponder) GetProof(ctx context.Context, domainName string, diff --git a/pkg/mapserver/updater/updater.go b/pkg/mapserver/updater/updater.go index 03df7cf5..aacd3a4a 100644 --- a/pkg/mapserver/updater/updater.go +++ b/pkg/mapserver/updater/updater.go @@ -65,6 +65,10 @@ func NewMapUpdater(config *db.Configuration, urls []string) (*MapUpdater, error) }, nil } +func (u *MapUpdater) GetProgress() (string, int, int, error) { + return u.Fetchers[u.currFetcher].URL(), int(u.lastState.Size), int(u.currState.Size), nil +} + // StartFetchingRemaining retrieves the last stored index number for this CT log server, and the // current last index, and uses them to call StartFetching. // It returns an error if there was one retrieving the start or end indices. @@ -107,19 +111,24 @@ func (u *MapUpdater) UpdateNextBatch(ctx context.Context) (int, error) { // Or we may want to insert batches and verify only the last one. // See issue #47 // https://github.com/netsec-ethz/fpki/issues/47 + start := time.Now() err = u.verifyValidity(ctx, certs, chains) if err != nil { return 0, fmt.Errorf("validity from CT log server: %w", err) } + fmt.Printf("Verified validity of %d certs in %.2f seconds at %s\n", len(certs), time.Now().Sub(start).Seconds(), getTime()) // Insert into DB. + start = time.Now() n, err := len(certs), u.updateCertBatch(ctx, certs, chains) if err != nil { return n, err } + fmt.Printf("Inserted %d certs into DB in %.2f seconds at %s\n", len(certs), time.Now().Sub(start).Seconds(), getTime()) // Store the last status obtained from the fetcher as updated. u.lastState.Size += uint64(n) + fmt.Printf("Storing new last status: %+v\n", u.lastState.Size) if u.lastState.Size == u.currState.Size { // Update the DB with all certs collected from this fetcher err = u.Conn.UpdateLastCTlogServerState(ctx, fetcher.URL(), @@ -574,3 +583,7 @@ func keyValuePairToSMTInput(keyValuePair []*db.KeyValuePair) ([][]byte, [][]byte return keyResult, valueResult, nil } + +func getTime() string { + return time.Now().UTC().Format(time.RFC3339) +} diff --git a/pkg/pca/pca.go b/pkg/pca/pca.go index 5eae7213..0648e10a 100644 --- a/pkg/pca/pca.go +++ b/pkg/pca/pca.go @@ -154,7 +154,10 @@ func (pca *PCA) SignAndLogRequest( if err != nil { return nil, err } - if !skip { + // TODO (cyrill): I think the cool off period should be enforced on the client and not the + // PCA. Unless the PCA performs additional verification if no signature from a valid policy + // certificate exists. For now, always skip the cooloff period + if false && !skip { return nil, fmt.Errorf("for now we don't support cool off periods; all requests must " + "be signed by the owner") } @@ -184,7 +187,6 @@ func (pca *PCA) SignAndLogRequest( // canSkipCoolOffPeriod verifies that the owner's signature is correct, if there is an owner's // signature in the request, and this PCA has the policy certificate used to signed said request. // It returns true if this PCA can skip the cool off period, false otherwise. -// TODO (cyrill): I think the cool off period should be enforced on the client and not the PCA. Unless the PCA performs additional verification if no signature from a valid policy certificate exists func (pca *PCA) canSkipCoolOffPeriod(req *common.PolicyCertificateSigningRequest) (bool, error) { // Owner's signature? if len(req.OwnerSignature) == 0 { diff --git a/pkg/util/pem.go b/pkg/util/pem.go index 698d2509..4204404c 100644 --- a/pkg/util/pem.go +++ b/pkg/util/pem.go @@ -2,11 +2,28 @@ package util import ( "crypto/rsa" + "encoding/base64" "fmt" ctx509 "github.com/google/certificate-transparency-go/x509" ) +func RSAPublicToDERBase64(pubKey *rsa.PublicKey) (string, error) { + derBytes, err := RSAPublicToDERBytes(pubKey) + if err != nil { + return "", fmt.Errorf("Failed to convert public key to DER format: %s", err) + } + return base64.StdEncoding.EncodeToString(derBytes), nil +} + +func DERBase64ToRSAPublic(base64PubKey string) (*rsa.PublicKey, error) { + der, err := base64.StdEncoding.DecodeString(base64PubKey) + if err != nil { + return nil, fmt.Errorf("Failed to decode base64 public key: %s", err) + } + return DERBytesToRSAPublic(der) +} + func RSAPublicToDERBytes(pubKey *rsa.PublicKey) ([]byte, error) { return ctx509.MarshalPKIXPublicKey(pubKey) } diff --git a/tests/integration/mapserver/main.go b/tests/integration/mapserver/main.go index 1e51c2f4..9612c41f 100644 --- a/tests/integration/mapserver/main.go +++ b/tests/integration/mapserver/main.go @@ -65,6 +65,7 @@ func mainFunc() int { DBConfig: dbConf, CertificatePemFile: "./tests/testdata/servercert.pem", PrivateKeyPemFile: "./tests/testdata/serverkey.pem", + HttpAPIPort: 8443, } // Mapserver: @@ -93,17 +94,17 @@ func mainFunc() int { rawPolicies[i] = raw } // Check responses for a.com, b.com and c.com . - checkResponse(t, client, "a.com", []common.SHA256Output{ + checkResponse(t, server.HttpAPIPort, client, "a.com", []common.SHA256Output{ common.SHA256Hash32Bytes(certs[0].Raw), common.SHA256Hash32Bytes(certs[1].Raw), common.SHA256Hash32Bytes(certs[2].Raw), common.SHA256Hash32Bytes(certs[3].Raw), }) - checkResponse(t, client, "b.com", []common.SHA256Output{ + checkResponse(t, server.HttpAPIPort, client, "b.com", []common.SHA256Output{ common.SHA256Hash32Bytes(rawPolicies[0]), common.SHA256Hash32Bytes(rawPolicies[1]), }) - checkResponse(t, client, "c.com", []common.SHA256Output{ + checkResponse(t, server.HttpAPIPort, client, "c.com", []common.SHA256Output{ common.SHA256Hash32Bytes(certs[4].Raw), common.SHA256Hash32Bytes(certs[5].Raw), common.SHA256Hash32Bytes(certs[6].Raw), @@ -117,6 +118,7 @@ func mainFunc() int { func checkResponse( t tests.T, + APIPort int, client *http.Client, domainName string, ids []common.SHA256Output, @@ -125,7 +127,7 @@ func checkResponse( // Proof of inclusion. t.Logf("verifying inclusion of %s", domainName) resp, err := client.Get(fmt.Sprintf("https://localhost:%d/getproof?domain=%s", - mapserver.APIPort, + APIPort, domainName, )) require.NoError(t, err) @@ -176,7 +178,7 @@ func checkResponse( collatedIDs += hex.EncodeToString(id[:]) } resp, err = client.Get(fmt.Sprintf("https://localhost:%d/%s?ids=%s", - mapserver.APIPort, + APIPort, group.fcn, collatedIDs, ))