Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update JSON request interface to accept base64 encoded string representing artifact hash #343

Merged
merged 6 commits into from
May 8, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
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")
}
}
36 changes: 33 additions & 3 deletions pkg/tests/build_test_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,49 @@ package tests

import (
"bytes"
"crypto"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"math/big"
"testing"

"github.com/digitorus/timestamp"
"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) {
r := bytes.NewReader(artifact)
haydentherapper marked this conversation as resolved.
Show resolved Hide resolved
h := hash.New()

b := make([]byte, h.Size())
for {
n, err := r.Read(b)
if err == io.EOF {
break
}

_, err = h.Write(b[:n])
if err != nil {
return "", fmt.Errorf("failed to create hash")
}
}
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