Skip to content

Commit

Permalink
Update JSON request interface to accept base64 encoded string represe…
Browse files Browse the repository at this point in the history
…nting artifact hash (#343)

* json requests include base64 encoded artifact hash

Signed-off-by: Meredith Lancaster <malancas@github.com>

* comment

Signed-off-by: Meredith Lancaster <malancas@github.com>

* update arg name

Signed-off-by: Meredith Lancaster <malancas@github.com>

* add test for invalid artifact hash encoding

Signed-off-by: Meredith Lancaster <malancas@github.com>

* update not about json body

Signed-off-by: Meredith Lancaster <malancas@github.com>

* pr feedback

Signed-off-by: Meredith Lancaster <malancas@github.com>

---------

Signed-off-by: Meredith Lancaster <malancas@github.com>
  • Loading branch information
malancas authored May 8, 2023
1 parent f961b75 commit 403a0a1
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 32 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,16 @@ The service expects the JSON body to be in the shape:

```
{
"artifact": "myblob",
"artifactHash": "<base64 encoded artifact hash>",
"certificates": true,
"hashAlgorithm": "sha256",
"nonce": 1123343434,
"tsaPolicyOID": "1.2.3.4"
}
```

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
Expand Down
29 changes: 13 additions & 16 deletions pkg/api/timestamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"bytes"
"crypto"
"encoding/asn1"
"encoding/base64"
"encoding/json"
"fmt"
"io"
Expand All @@ -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"`
Expand Down Expand Up @@ -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) {
Expand Down
60 changes: 48 additions & 12 deletions pkg/tests/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"crypto/sha256"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/json"
"io"
"math/big"
"strings"
Expand Down Expand Up @@ -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{
Expand All @@ -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,
},
}

Expand Down Expand Up @@ -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,
Expand All @@ -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,
},
Expand Down Expand Up @@ -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{
Expand All @@ -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),
},
}

Expand Down Expand Up @@ -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,
Expand All @@ -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"),
},
}

Expand Down Expand Up @@ -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")
}
}
21 changes: 18 additions & 3 deletions pkg/tests/build_test_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package tests

import (
"bytes"
"crypto"
"encoding/base64"
"encoding/json"
"math/big"
"testing"
Expand All @@ -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,
}
Expand Down

0 comments on commit 403a0a1

Please sign in to comment.