Compare commits

...

7 Commits
v0.2.0 ... main

Author SHA1 Message Date
Junjie Gao 6c337f1e30
chore: update maintainer list: Junjie Gao retired (#46)
Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
2025-06-30 17:46:13 +08:00
Patrick Zheng 4f55b14d9f
bump: upgrade go version to v1.23.0 (#44)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-03-06 14:37:39 +08:00
Patrick Zheng 543cd58803
chore: add patch to codecov (#42)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-30 08:44:48 +08:00
Junjie Gao 63a40da507
fix(test): unit test failed on GOARCH=386 (#41)
**What this PR does / why we need it**:
To fix the failed test on 32bit platforms

**Which issue(s) this PR resolves**:
Resolves #40 

**Please check the following list**:
- [x]  Does the affected code have corresponding tests, e.g. unit test?
- [ ] Does this introduce breaking changes that would require an
announcement or bumping the major version?
- [ ]  Do all new files have an appropriate license header?

<!-- If this is a security issue, please do not discuss on GitHub.
Please report any suspected or confirmed security issues directly to
https://github.com/notaryproject/tspclient-go/blob/main/CODEOWNERS. -->

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
2024-12-23 09:41:54 +08:00
Patrick Zheng 90a141e752
cut!: remove nonce options (#34)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-10-30 09:53:23 +08:00
Patrick Zheng 718b4b8a7c
fix: add more tsa url sanity check (#37)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-10-29 08:53:21 +08:00
Junjie Gao a614afb557
fix: CMS content type check (#35)
**What this PR does / why we need it**:
Previously, the CMS package was generically designed for RFC 5652.
However, since the CMS package will not be exported and we only need to
care about RFC 3161, we can restrict the CMS content type to TSTInfo
(id-ct-TSTInfo) to fail fast.

**Which issue(s) this PR resolves** *(optional, in `resolves #<issue
number>(, resolves #<issue_number>, ...)` format, will close the
issue(s) when PR gets merged)*:
Resolves #33

**Please check the following list**:
- [x]  Does the affected code have corresponding tests, e.g. unit test?
- [ ] Does this introduce breaking changes that would require an
announcement or bumping the major version?
- [ ]  Do all new files have an appropriate license header?

<!-- If this is a security issue, please do not discuss on GitHub.
Please report any suspected or confirmed security issues directly to
https://github.com/notaryproject/tspclient-go/blob/main/CODEOWNERS. -->

---------

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
2024-10-29 08:34:21 +08:00
15 changed files with 81 additions and 44 deletions

View File

@ -14,5 +14,8 @@
coverage:
status:
project:
default:
target: 80%
patch:
default:
target: 80%

View File

@ -1,3 +1,3 @@
# Repo-Level Owners (in alphabetical order)
# Note: This is only for the notaryproject/tspclient-go repo
* @gokarnm @JeyJeyGao @niazfk @priteshbandi @shizhMSFT @toddysm @Two-Hearts @vaninrao10 @yizha1
* @gokarnm @niazfk @priteshbandi @shizhMSFT @toddysm @Two-Hearts @vaninrao10 @yizha1

View File

@ -10,10 +10,12 @@ Yi Zha <yizha1@microsoft.com> (@yizha1)
# Repo-Level Maintainers (in alphabetical order)
# Pattern: [First Name] [Last Name] <[Email Address]> ([GitHub Handle])
# Note: This is for the notaryproject/tspclient-go repo
Junjie Gao <junjiegao@microsoft.com> (@JeyJeyGao)
Patrick Zheng <patrickzheng@microsoft.com> (@Two-Hearts)
Shiwei Zhang <shizh@microsoft.com> (@shizhMSFT)
# Emeritus Org Maintainers (in alphabetical order)
Justin Cormack <justin.cormack@docker.com> (@justincormack)
Steve Lasker <StevenLasker@hotmail.com> (@stevelasker)
Steve Lasker <StevenLasker@hotmail.com> (@stevelasker)
# Emeritus Repo-Level Maintainers (in alphabetical order)
Junjie Gao <junjiegao@microsoft.com> (@JeyJeyGao)

View File

@ -73,14 +73,9 @@ func TestTSATimestampGranted(t *testing.T) {
// do timestamp
message := []byte("notation")
nonce, err := generateNonce()
if err != nil {
t.Fatal("failed to create nonce:", err)
}
requestOpts := RequestOptions{
Content: message,
HashAlgorithm: crypto.SHA256,
Nonce: nonce,
}
req, err := NewRequest(requestOpts)
if err != nil {

2
go.mod
View File

@ -1,3 +1,3 @@
module github.com/notaryproject/tspclient-go
go 1.21
go 1.23.0

View File

@ -53,9 +53,16 @@ func NewHTTPTimestamper(httpClient *http.Client, endpoint string) (Timestamper,
if httpClient == nil {
httpClient = &http.Client{Timeout: 5 * time.Second}
}
if _, err := url.Parse(endpoint); err != nil {
tsaURL, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
if tsaURL.Scheme != "http" && tsaURL.Scheme != "https" {
return nil, fmt.Errorf("endpoint %q: scheme must be http or https, but got %q", endpoint, tsaURL.Scheme)
}
if tsaURL.Host == "" {
return nil, fmt.Errorf("endpoint %q: host cannot be empty", endpoint)
}
return &httpTimestamper{
httpClient: httpClient,
endpoint: endpoint,

View File

@ -67,12 +67,12 @@ func TestHTTPTimestampGranted(t *testing.T) {
Tag: 5,
FullBytes: []byte{5, 0},
},
NoNonce: true,
}
req, err := NewRequest(requestOpts)
if err != nil {
t.Fatalf("NewRequest() error = %v", err)
}
req.Nonce = nil
ctx := context.Background()
resp, err := tsa.Timestamp(ctx, req)
if err != nil {
@ -256,6 +256,24 @@ func TestNewHTTPTimestamper(t *testing.T) {
if _, err := NewHTTPTimestamper(nil, malformedURL); err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected error %s, but got %v", expectedErrMsg, err)
}
malformedURL = "invalid"
expectedErrMsg = `endpoint "invalid": scheme must be http or https, but got ""`
if _, err := NewHTTPTimestamper(nil, malformedURL); err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected error %s, but got %v", expectedErrMsg, err)
}
malformedURL = "invalid://"
expectedErrMsg = `endpoint "invalid://": scheme must be http or https, but got "invalid"`
if _, err := NewHTTPTimestamper(nil, malformedURL); err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected error %s, but got %v", expectedErrMsg, err)
}
malformedURL = "https://"
expectedErrMsg = `endpoint "https://": host cannot be empty`
if _, err := NewHTTPTimestamper(nil, malformedURL); err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected error %s, but got %v", expectedErrMsg, err)
}
}
func TestHttpTimestamperTimestamp(t *testing.T) {

View File

@ -248,14 +248,19 @@ func (d *ParsedSignedData) verifySignedAttributes(signerInfo *SignerInfo, chains
// verify attributes if present
if len(signerInfo.SignedAttributes) == 0 {
if d.ContentType.Equal(oid.Data) {
return nil, nil
}
// signed attributes MUST be present if the content type of the
// EncapsulatedContentInfo value being signed is not id-data.
// According to RFC 5652, if the Content Type is id-data, signed
// attributes can be empty. However, this cms package is designed for
// timestamp (RFC 3161) and the content type must be id-ct-TSTInfo,
// so we require signed attributes to be present.
return nil, VerificationError{Message: "missing signed attributes"}
}
// this CMS package is designed for timestamping (RFC 3161), so checking the
// content type to be id-ct-TSTInfo is an optimization for tspclient to
// fail fast.
if !oid.TSTInfo.Equal(d.ContentType) {
return nil, fmt.Errorf("unexpected content type: %v. Expected to be id-ct-TSTInfo (%v)", d.ContentType, oid.TSTInfo)
}
var contentType asn1.ObjectIdentifier
if err := signerInfo.SignedAttributes.Get(oid.ContentType, &contentType); err != nil {
return nil, VerificationError{Message: "invalid content type", Detail: err}

View File

@ -160,7 +160,12 @@ func TestVerify(t *testing.T) {
{
name: "id-data content type without signed attributes",
filePath: "testdata/SignedDataWithoutSignedAttributes.p7s",
wantErr: false,
wantErr: true,
},
{
name: "invalid content type",
filePath: "testdata/TimeStampTokenWithInvalidContentType.p7s",
wantErr: true,
},
{
name: "an invalid and a valid signer info",

Binary file not shown.

View File

@ -19,7 +19,7 @@ import (
)
func TestConvertToDER(t *testing.T) {
var testBytes = make([]byte, 0xFFFFFFFF+8)
var testBytes = make([]byte, 0x7FFFFFFF)
// primitive identifier
testBytes[0] = 0x1f
testBytes[1] = 0xa0

View File

@ -94,18 +94,6 @@ type RequestOptions struct {
// Reference: https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.1
ReqPolicy asn1.ObjectIdentifier
// NoNonce disables any Nonce usage. When set to true, the Nonce field is
// ignored, and no built-in Nonce will be generated. OPTIONAL.
NoNonce bool
// Nonce is a large random number with a high probability that the client
// generates it only once. The same nonce is included and validated in the
// response. It is only used when NoNonce is not set to true.
//
// When this field is nil, a built-in Nonce will be generated and sent to
// the TSA. OPTIONAL.
Nonce *big.Int
// NoCert tells the TSA to not include any signing certificate in its
// response. By default, TSA signing certificate is included in the response.
// OPTIONAL.
@ -133,17 +121,9 @@ func NewRequest(opts RequestOptions) (*Request, error) {
if tspclientasn1.EqualRawValue(hashAlgParameter, asn1.RawValue{}) || tspclientasn1.EqualRawValue(hashAlgParameter, asn1.NullRawValue) {
hashAlgParameter = asn1NullRawValue
}
var nonce *big.Int
if !opts.NoNonce {
if opts.Nonce != nil { // user provided Nonce, use it
nonce = opts.Nonce
} else { // user ignored Nonce, use built-in Nonce
var err error
nonce, err = generateNonce()
if err != nil {
return nil, &MalformedRequestError{Msg: err.Error()}
}
}
nonce, err := generateNonce()
if err != nil {
return nil, &MalformedRequestError{Msg: err.Error()}
}
return &Request{
Version: 1,
@ -206,7 +186,7 @@ func generateNonce() (*big.Int, error) {
// Pick a random number from 0 to 2^159
nonce, err := rand.Int(rand.Reader, (&big.Int{}).Lsh(big.NewInt(1), 159))
if err != nil {
return nil, errors.New("error generating nonce")
return nil, errors.New("failed to generate nonce")
}
return nonce, nil
}

View File

@ -15,6 +15,7 @@ package tspclient
import (
"crypto"
"crypto/rand"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
@ -71,6 +72,21 @@ func TestNewRequest(t *testing.T) {
if !reflect.DeepEqual(req.MessageImprint.HashAlgorithm.Parameters, asn1NullRawValue) {
t.Fatalf("expected %v, but got %v", asn1NullRawValue, req.MessageImprint.HashAlgorithm.Parameters)
}
defaultRandReader := rand.Reader
rand.Reader = &dummyRandReader{}
defer func() {
rand.Reader = defaultRandReader
}()
opts = RequestOptions{
Content: message,
HashAlgorithm: crypto.SHA256,
}
expectedErrMsg = "malformed timestamping request: failed to generate nonce"
_, err = NewRequest(opts)
if err == nil || !errors.As(err, &malformedRequest) || err.Error() != expectedErrMsg {
t.Fatalf("expected error %s, but got %v", expectedErrMsg, err)
}
}
func TestRequestMarshalBinary(t *testing.T) {
@ -155,3 +171,9 @@ func TestValidateRequest(t *testing.T) {
t.Fatalf("expected error %s, but got %v", expectedErrMsg, err)
}
}
type dummyRandReader struct{}
func (r *dummyRandReader) Read(b []byte) (int, error) {
return 0, errors.New("failed to read")
}

View File

@ -45,7 +45,7 @@ func ParseSignedToken(berData []byte) (*SignedToken, error) {
return nil, err
}
if !oid.TSTInfo.Equal(signed.ContentType) {
return nil, fmt.Errorf("unexpected content type: %v", signed.ContentType)
return nil, fmt.Errorf("unexpected content type: %v. Expected to be id-ct-TSTInfo (%v)", signed.ContentType, oid.TSTInfo)
}
return (*SignedToken)(signed), nil
}

View File

@ -30,7 +30,7 @@ func TestParseSignedToken(t *testing.T) {
if err != nil {
t.Fatal(err)
}
expectedErrMsg := fmt.Sprintf("unexpected content type: %v", oid.Data)
expectedErrMsg := fmt.Sprintf("unexpected content type: %v. Expected to be id-ct-TSTInfo (1.2.840.113549.1.9.16.1.4)", oid.Data)
_, err = ParseSignedToken(timestampToken)
if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected error %s, but got %v", expectedErrMsg, err)