diff --git a/README.md b/README.md index 3e8fb62e..5668e143 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ The service expects the JSON body to be in the shape: ``` { - "artifact": "myblob", + "artifactHash": "", "certificates": true, "hashAlgorithm": "sha256", "nonce": 1123343434, @@ -109,6 +109,8 @@ The service expects the JSON body to be in the shape: } ``` +The artifact hash must be represented as a base64 encoded string. + ## Production deployment To deploy to production, the timestamp authority currently supports signing with Cloud KMS or diff --git a/pkg/api/timestamp.go b/pkg/api/timestamp.go index 6e92de70..cbedb1ef 100644 --- a/pkg/api/timestamp.go +++ b/pkg/api/timestamp.go @@ -18,6 +18,7 @@ import ( "bytes" "crypto" "encoding/asn1" + "encoding/base64" "encoding/json" "fmt" "io" @@ -35,7 +36,7 @@ import ( ) type JSONRequest struct { - Artifact string `json:"artifact"` + ArtifactHash string `json:"artifactHash"` Certificates bool `json:"certificates"` HashAlgorithm string `json:"hashAlgorithm"` Nonce *big.Int `json:"nonce"` @@ -83,26 +84,22 @@ func ParseJSONRequest(reqBytes []byte) (*timestamp.Request, string, error) { } } - opts := timestamp.RequestOptions{ - Certificates: req.Certificates, - Hash: hashAlgo, - Nonce: req.Nonce, - TSAPolicyOID: oidInts, - } - - // create a DER encocded timestamp request from the reader and timestamp.RequestOptions - tsReqBytes, err := timestamp.CreateRequest(bytes.NewBuffer([]byte(req.Artifact)), &opts) + // decode the base64 encoded artifact hash + decoded, err := base64.StdEncoding.DecodeString(req.ArtifactHash) if err != nil { - return nil, failedToGenerateTimestampResponse, fmt.Errorf("failed to create Request from JSON: %v", err) + return nil, failedToGenerateTimestampResponse, fmt.Errorf("failed to decode base64 encoded artifact hash: %v", err) } - // parse the DER encoded timestamp request into a timestamp.Request struct - tsRequest, err := timestamp.ParseRequest(tsReqBytes) - if err != nil { - return nil, failedToGenerateTimestampResponse, fmt.Errorf("failed to parse Request from Request bytes: %v", err) + // create a timestamp request from the request's JSON body + tsReq := timestamp.Request{ + HashAlgorithm: hashAlgo, + HashedMessage: decoded, + Certificates: req.Certificates, + Nonce: req.Nonce, + TSAPolicyOID: oidInts, } - return tsRequest, "", nil + return &tsReq, "", nil } func parseDERRequest(reqBytes []byte) (*timestamp.Request, string, error) { diff --git a/pkg/tests/api_test.go b/pkg/tests/api_test.go index fbf775d8..758f173a 100644 --- a/pkg/tests/api_test.go +++ b/pkg/tests/api_test.go @@ -20,6 +20,7 @@ import ( "crypto/sha256" "crypto/x509/pkix" "encoding/asn1" + "encoding/json" "io" "math/big" "strings" @@ -88,13 +89,13 @@ func TestGetTimestampResponse(t *testing.T) { testArtifact := "blobblobblobblobblobblobblobblobblob" testNonce := big.NewInt(1234) includeCerts := true - testHashStr := "sha256" - testHash := crypto.SHA256 + hashFunc := crypto.SHA256 + hashName := "sha256" opts := ts.RequestOptions{ Nonce: testNonce, Certificates: includeCerts, TSAPolicyOID: nil, - Hash: testHash, + Hash: hashFunc, } tests := []timestampTestCase{ @@ -104,15 +105,15 @@ func TestGetTimestampResponse(t *testing.T) { reqBytes: buildTimestampQueryReq(t, []byte(testArtifact), opts), nonce: testNonce, includeCerts: includeCerts, - hash: testHash, + hash: hashFunc, }, { name: "JSON Request", reqMediaType: client.JSONMediaType, - reqBytes: buildJSONReq(t, []byte(testArtifact), includeCerts, testHashStr, testNonce, ""), + reqBytes: buildJSONReq(t, []byte(testArtifact), hashFunc, hashName, includeCerts, testNonce, ""), nonce: testNonce, includeCerts: includeCerts, - hash: testHash, + hash: hashFunc, }, } @@ -197,7 +198,8 @@ func TestGetTimestampResponseWithExtsAndOID(t *testing.T) { testPolicyOID := asn1.ObjectIdentifier{1, 2, 3, 4, 5} oidStr := "1.2.3.4.5" includeCerts := true - testHashStr := "sha256" + hashFunc := crypto.SHA256 + hashName := "sha256" opts := ts.RequestOptions{ Nonce: testNonce, @@ -216,7 +218,7 @@ func TestGetTimestampResponseWithExtsAndOID(t *testing.T) { { name: "JSON Request", reqMediaType: client.JSONMediaType, - reqBytes: buildJSONReq(t, []byte(testArtifact), includeCerts, testHashStr, testNonce, oidStr), + reqBytes: buildJSONReq(t, []byte(testArtifact), hashFunc, hashName, includeCerts, testNonce, oidStr), nonce: testNonce, policyOID: testPolicyOID, }, @@ -283,7 +285,8 @@ func TestGetTimestampResponseWithExtsAndOID(t *testing.T) { func TestGetTimestampResponseWithNoCertificateOrNonce(t *testing.T) { testArtifact := "blob" includeCerts := false - testHashStr := "sha256" + hashFunc := crypto.SHA256 + hashName := "sha256" oidStr := "1.2.3.4" opts := ts.RequestOptions{ @@ -300,7 +303,7 @@ func TestGetTimestampResponseWithNoCertificateOrNonce(t *testing.T) { { name: "JSON Request", reqMediaType: client.JSONMediaType, - reqBytes: buildJSONReq(t, []byte(testArtifact), includeCerts, testHashStr, nil, oidStr), + reqBytes: buildJSONReq(t, []byte(testArtifact), hashFunc, hashName, includeCerts, nil, oidStr), }, } @@ -346,7 +349,8 @@ func TestGetTimestampResponseWithNoCertificateOrNonce(t *testing.T) { func TestUnsupportedHashAlgorithm(t *testing.T) { testArtifact := "blob" - testHashStr := "sha1" + hashFunc := crypto.SHA1 + hashName := "sha1" opts := ts.RequestOptions{ Hash: crypto.SHA1, @@ -361,7 +365,7 @@ func TestUnsupportedHashAlgorithm(t *testing.T) { { name: "JSON Request", reqMediaType: client.JSONMediaType, - reqBytes: buildJSONReq(t, []byte(testArtifact), false, testHashStr, nil, "1.2.3.4"), + reqBytes: buildJSONReq(t, []byte(testArtifact), hashFunc, hashName, false, nil, "1.2.3.4"), }, } @@ -391,3 +395,35 @@ func TestUnsupportedHashAlgorithm(t *testing.T) { } } } + +func TestInvalidJSONArtifactHashNotBase64Encoded(t *testing.T) { + jsonReq := api.JSONRequest{ + HashAlgorithm: "sha256", + ArtifactHash: "not*base64*encoded", + } + + marshalled, err := json.Marshal(jsonReq) + if err != nil { + t.Fatalf("failed to marshal request") + } + + url := createServer(t) + + c, err := client.GetTimestampClient(url, client.WithContentType(client.JSONMediaType)) + if err != nil { + t.Fatalf("unexpected error creating client: %v", err) + } + + params := timestamp.NewGetTimestampResponseParams() + params.SetTimeout(10 * time.Second) + params.Request = io.NopCloser(bytes.NewReader(marshalled)) + + var respBytes bytes.Buffer + clientOption := func(op *runtime.ClientOperation) { + op.ConsumesMediaTypes = []string{client.JSONMediaType} + } + _, err = c.Timestamp.GetTimestampResponse(params, &respBytes, clientOption) + if err == nil { + t.Fatalf("expected error to occur while parsing request") + } +} diff --git a/pkg/tests/build_test_data.go b/pkg/tests/build_test_data.go index 597547e2..03ba974a 100644 --- a/pkg/tests/build_test_data.go +++ b/pkg/tests/build_test_data.go @@ -16,6 +16,8 @@ package tests import ( "bytes" + "crypto" + "encoding/base64" "encoding/json" "math/big" "testing" @@ -24,11 +26,24 @@ import ( "github.com/sigstore/timestamp-authority/pkg/api" ) -func buildJSONReq(t *testing.T, artifact []byte, includeCerts bool, hashAlg string, nonce *big.Int, oidStr string) []byte { +func createBase64EncodedArtifactHash(artifact []byte, hash crypto.Hash) (string, error) { + h := hash.New() + h.Write(artifact) + artifactHash := h.Sum(nil) + + return base64.StdEncoding.EncodeToString(artifactHash), nil +} + +func buildJSONReq(t *testing.T, artifact []byte, digestHash crypto.Hash, hashName string, includeCerts bool, nonce *big.Int, oidStr string) []byte { + encodedHash, err := createBase64EncodedArtifactHash(artifact, digestHash) + if err != nil { + t.Fatalf("failed to marshal request") + } + jsonReq := api.JSONRequest{ Certificates: includeCerts, - HashAlgorithm: hashAlg, - Artifact: string(artifact), + HashAlgorithm: hashName, + ArtifactHash: encodedHash, Nonce: nonce, TSAPolicyOID: oidStr, }