Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion signer/contentsignaturepki/contentsignature.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"hash"
"io"
"math/big"
"net/http"
"time"

"github.com/mozilla-services/autograph/database"
Expand Down Expand Up @@ -185,7 +186,7 @@ func (s *ContentSigner) initEE(conf signer.Configuration) error {
default:
return fmt.Errorf("contentsignaturepki %q: failed to find suitable end-entity: %w", s.ID, err)
}
_, _, err = GetX5U(buildHTTPClient(), s.X5U)
_, _, err = GetX5U(http.DefaultClient, s.X5U)
if err != nil {
return fmt.Errorf("contentsignaturepki %q: failed to verify x5u: %w", s.ID, err)
}
Expand Down
126 changes: 65 additions & 61 deletions signer/contentsignaturepki/contentsignature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ package contentsignaturepki
import (
"crypto/ecdsa"
"errors"
"fmt"
"net/http"
"strings"
"testing"

Expand All @@ -19,73 +21,75 @@ import (
func TestSign(t *testing.T) {
input := []byte("foobarbaz1234abcd")
for i, testcase := range PASSINGTESTCASES {
// initialize a signer
s, err := New(testcase.cfg)
if err != nil {
t.Fatalf("testcase %d signer initialization failed with: %v", i, err)
}
if s.Type != testcase.cfg.Type {
t.Fatalf("testcase %d signer type %q does not match configuration %q", i, s.Type, testcase.cfg.Type)
}
if s.ID != testcase.cfg.ID {
t.Fatalf("testcase %d signer id %q does not match configuration %q", i, s.ID, testcase.cfg.ID)
}
if s.PrivateKey != testcase.cfg.PrivateKey {
t.Fatalf("testcase %d signer private key %q does not match configuration %q", i, s.PrivateKey, testcase.cfg.PrivateKey)
}
if s.Mode != testcase.cfg.Mode {
t.Fatalf("testcase %d signer curve %q does not match expected %q", i, s.Mode, testcase.cfg.Mode)
}
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
// initialize a signer
s, err := New(testcase.cfg)
if err != nil {
t.Fatalf("testcase %d signer initialization failed with: %v", i, err)
}
if s.Type != testcase.cfg.Type {
t.Fatalf("testcase %d signer type %q does not match configuration %q", i, s.Type, testcase.cfg.Type)
}
if s.ID != testcase.cfg.ID {
t.Fatalf("testcase %d signer id %q does not match configuration %q", i, s.ID, testcase.cfg.ID)
}
if s.PrivateKey != testcase.cfg.PrivateKey {
t.Fatalf("testcase %d signer private key %q does not match configuration %q", i, s.PrivateKey, testcase.cfg.PrivateKey)
}
if s.Mode != testcase.cfg.Mode {
t.Fatalf("testcase %d signer curve %q does not match expected %q", i, s.Mode, testcase.cfg.Mode)
}

// sign input data
sig, err := s.SignData(input, nil)
if err != nil {
t.Fatalf("testcase %d failed to sign data: %v", i, err)
}
// convert signature to string format
sigstr, err := sig.Marshal()
if err != nil {
t.Fatalf("testcase %d failed to marshal signature: %v", i, err)
}
// sign input data
sig, err := s.SignData(input, nil)
if err != nil {
t.Fatalf("testcase %d failed to sign data: %v", i, err)
}
// convert signature to string format
sigstr, err := sig.Marshal()
if err != nil {
t.Fatalf("testcase %d failed to marshal signature: %v", i, err)
}

// convert string format back to signature
cs, err := verifier.Unmarshal(sigstr)
if err != nil {
t.Fatalf("testcase %d failed to unmarshal signature: %v", i, err)
}
// convert string format back to signature
cs, err := verifier.Unmarshal(sigstr)
if err != nil {
t.Fatalf("testcase %d failed to unmarshal signature: %v", i, err)
}

// make sure we still have the same string representation
sigstr2, err := cs.Marshal()
if err != nil {
t.Fatalf("testcase %d failed to re-marshal signature: %v", i, err)
}
if sigstr != sigstr2 {
t.Fatalf("testcase %d marshalling signature changed its format.\nexpected\t%q\nreceived\t%q",
i, sigstr, sigstr2)
}
// make sure we still have the same string representation
sigstr2, err := cs.Marshal()
if err != nil {
t.Fatalf("testcase %d failed to re-marshal signature: %v", i, err)
}
if sigstr != sigstr2 {
t.Fatalf("testcase %d marshalling signature changed its format.\nexpected\t%q\nreceived\t%q",
i, sigstr, sigstr2)
}

if cs.Len != getSignatureLen(s.Mode) {
t.Fatalf("testcase %d expected signature len of %d, got %d",
i, getSignatureLen(s.Mode), cs.Len)
}
if cs.Mode != s.Mode {
t.Fatalf("testcase %d expected curve name %q, got %q", i, s.Mode, cs.Mode)
}
if cs.Len != getSignatureLen(s.Mode) {
t.Fatalf("testcase %d expected signature len of %d, got %d",
i, getSignatureLen(s.Mode), cs.Len)
}
if cs.Mode != s.Mode {
t.Fatalf("testcase %d expected curve name %q, got %q", i, s.Mode, cs.Mode)
}

// verify the signature using the public key of the end entity
_, certs, err := GetX5U(buildHTTPClient(), s.X5U)
if err != nil {
t.Fatalf("testcase %d failed to get X5U %q: %v", i, s.X5U, err)
}
leaf := certs[0]
key := leaf.PublicKey.(*ecdsa.PublicKey)
if !sig.(*verifier.ContentSignature).VerifyData([]byte(input), key) {
t.Fatalf("testcase %d failed to verify signature", i)
}
// verify the signature using the public key of the end entity
_, certs, err := GetX5U(http.DefaultClient, s.X5U)
if err != nil {
t.Fatalf("testcase %d failed to get X5U %q: %v", i, s.X5U, err)
}
leaf := certs[0]
key := leaf.PublicKey.(*ecdsa.PublicKey)
if !sig.(*verifier.ContentSignature).VerifyData([]byte(input), key) {
t.Fatalf("testcase %d failed to verify signature", i)
}

if leaf.Subject.CommonName != testcase.expectedCommonName {
t.Errorf("testcase %d expected common name %#v, got %#v", i, testcase.expectedCommonName, leaf.Subject.CommonName)
}
if leaf.Subject.CommonName != testcase.expectedCommonName {
t.Errorf("testcase %d expected common name %#v, got %#v", i, testcase.expectedCommonName, leaf.Subject.CommonName)
}
})
}
}

Expand Down
66 changes: 34 additions & 32 deletions signer/contentsignaturepki/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/url"
"os"
"path"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -82,56 +83,57 @@ func writeLocalFile(data, name string, target *url.URL) error {
return err
}
}
// write the file into the target dir
return os.WriteFile(target.Path+name, []byte(data), 0755)
}

// buildHTTPClient returns the default HTTP.Client for fetching X5Us
func buildHTTPClient() *http.Client {
return &http.Client{}
return os.WriteFile(filepath.Join(target.Path, name), []byte(data), 0755)
}

// GetX5U retrieves a chain file of certs from upload location, parses
// and verifies it, then returns a byte slice of the response body and
// a slice of parsed certificates.
func GetX5U(client *http.Client, x5u string) (body []byte, certs []*x509.Certificate, err error) {
func GetX5U(client *http.Client, x5u string) ([]byte, []*x509.Certificate, error) {
parsedURL, err := url.Parse(x5u)
if err != nil {
err = fmt.Errorf("failed to parse chain upload location: %w", err)
return
}
if parsedURL.Scheme == "file" {
t := &http.Transport{}
t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
client.Transport = t
}
resp, err := client.Get(x5u)
if err != nil {
err = fmt.Errorf("failed to retrieve x5u: %w", err)
return
return nil, nil, fmt.Errorf("failed to parse chain upload location: %w", err)

}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("failed to retrieve x5u from %s: %s", x5u, resp.Status)
return
var bodyReader io.ReadCloser
switch parsedURL.Scheme {
case "https", "http":
resp, err := client.Get(x5u)
if err != nil {
return nil, nil, fmt.Errorf("failed to retrieve x5u from %#v: %w", x5u, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, nil, fmt.Errorf("failed to retrieve x5u from %#v: %s", x5u, resp.Status)
}
bodyReader = resp.Body

case "file":
bodyReader, err = os.Open(parsedURL.Path)
if err != nil {
return nil, nil, fmt.Errorf("failed to open x5u file:// at %#v: %w", x5u, err)
}
defer bodyReader.Close()
default:
return nil, nil, fmt.Errorf("unsupported x5u scheme: %#v", parsedURL.Scheme)
}
body, err = io.ReadAll(resp.Body)

body, err := io.ReadAll(bodyReader)
if err != nil {
err = fmt.Errorf("failed to parse x5u body: %w", err)
return
return nil, nil, fmt.Errorf("failed to parse x5u body from %#v: %w", x5u, err)
}
certs, err = csigverifier.ParseChain(body)
certs, err := csigverifier.ParseChain(body)
if err != nil {
err = fmt.Errorf("failed to parse x5u : %w", err)
return

return nil, nil, fmt.Errorf("failed to parse x5u : %w", err)
}
rootHash := sha2Fingerprint(certs[2])
err = csigverifier.VerifyChain([]string{rootHash}, certs, time.Now())
if err != nil {
err = fmt.Errorf("failed to verify certificate chain: %w", err)
return
return nil, nil, fmt.Errorf("failed to verify certificate chain: %w", err)
}
return
return body, certs, nil
}

func sha2Fingerprint(cert *x509.Certificate) string {
Expand Down
16 changes: 10 additions & 6 deletions signer/contentsignaturepki/x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"encoding/pem"
"fmt"
"math/big"
"net/http"
"net/url"
"time"

"github.com/mozilla-services/autograph/database"
Expand Down Expand Up @@ -41,23 +43,25 @@ func (s *ContentSigner) findAndSetEE(conf signer.Configuration) (err error) {

// makeAndUploadChain makes a certificate using the end-entity public key,
// uploads the chain to its destination and creates an X5U download URL
func (s *ContentSigner) makeAndUploadChain() (err error) {
var fullChain, chainName string
fullChain, chainName, err = s.makeChain()
func (s *ContentSigner) makeAndUploadChain() error {
fullChain, chainName, err := s.makeChain()
if err != nil {
return fmt.Errorf("failed to make chain: %w", err)
}
err = s.upload(fullChain, chainName)
if err != nil {
return fmt.Errorf("failed to upload chain: %w", err)
}
newX5U := s.X5U + chainName
_, _, err = GetX5U(buildHTTPClient(), newX5U)
newX5U, err := url.JoinPath(s.X5U, chainName)
if err != nil {
return fmt.Errorf("failed to join x5u with chain name: %w", err)
}
_, _, err = GetX5U(http.DefaultClient, newX5U)
if err != nil {
return fmt.Errorf("failed to download new chain: %w", err)
}
s.X5U = newX5U
return
return nil
}

// makeChain issues an end-entity certificate using the ca private key and the first
Expand Down
Loading