diff --git a/internal/crypto/cms/cms.go b/internal/crypto/cms/cms.go deleted file mode 100644 index a1cec15c..00000000 --- a/internal/crypto/cms/cms.go +++ /dev/null @@ -1,92 +0,0 @@ -// Package cms verifies signatures in Cryptographic Message Syntax (CMS) / PKCS7 -// defined in RFC 5652. -package cms - -import ( - "crypto/x509/pkix" - "encoding/asn1" - "math/big" -) - -// ContentInfo ::= SEQUENCE { -// contentType ContentType, -// content [0] EXPLICIT ANY DEFINED BY contentType } -type ContentInfo struct { - ContentType asn1.ObjectIdentifier - Content asn1.RawValue `asn1:"explicit,tag:0"` -} - -// SignedData ::= SEQUENCE { -// version CMSVersion, -// digestAlgorithms DigestAlgorithmIdentifiers, -// encapContentInfo EncapsulatedContentInfo, -// certificates [0] IMPLICIT CertificateSet OPTIONAL, -// crls [1] IMPLICIT CertificateRevocationLists OPTIONAL, -// signerInfos SignerInfos } -type SignedData struct { - Version int - DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"` - EncapsulatedContentInfo EncapsulatedContentInfo - Certificates asn1.RawValue `asn1:"optional,tag:0"` - CRLs []pkix.CertificateList `asn1:"optional,tag:1"` - SignerInfos []SignerInfo `asn1:"set"` -} - -// EncapsulatedContentInfo ::= SEQUENCE { -// eContentType ContentType, -// eContent [0] EXPLICIT OCTET STRING OPTIONAL } -type EncapsulatedContentInfo struct { - ContentType asn1.ObjectIdentifier - Content []byte `asn1:"explicit,optional,tag:0"` -} - -// SignerInfo ::= SEQUENCE { -// version CMSVersion, -// sid SignerIdentifier, -// digestAlgorithm DigestAlgorithmIdentifier, -// signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL, -// signatureAlgorithm SignatureAlgorithmIdentifier, -// signature SignatureValue, -// unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL } -// Only version 1 is supported. As defined in RFC 5652 5.3, SignerIdentifier -// is IssuerAndSerialNumber when version is 1. -type SignerInfo struct { - Version int - SignerIdentifier IssuerAndSerialNumber - DigestAlgorithm pkix.AlgorithmIdentifier - SignedAttributes Attributes `asn1:"optional,tag:0"` - SignatureAlgorithm pkix.AlgorithmIdentifier - Signature []byte - UnsignedAttributes Attributes `asn1:"optional,tag:1"` -} - -// IssuerAndSerialNumber ::= SEQUENCE { -// issuer Name, -// serialNumber CertificateSerialNumber } -type IssuerAndSerialNumber struct { - Issuer asn1.RawValue - SerialNumber *big.Int -} - -// Attribute ::= SEQUENCE { -// attrType OBJECT IDENTIFIER, -// attrValues SET OF AttributeValue } -type Attribute struct { - Type asn1.ObjectIdentifier - Values asn1.RawValue `asn1:"set"` -} - -// Attribute ::= SET SIZE (1..MAX) OF Attribute -type Attributes []Attribute - -// TryGet tries to find the attribute by the given identifier, parse and store -// the result in the value pointed to by out. -func (a Attributes) TryGet(identifier asn1.ObjectIdentifier, out interface{}) error { - for _, attribute := range a { - if identifier.Equal(attribute.Type) { - _, err := asn1.Unmarshal(attribute.Values.Bytes, out) - return err - } - } - return ErrAttributeNotFound -} diff --git a/internal/crypto/cms/errors.go b/internal/crypto/cms/errors.go deleted file mode 100644 index d1fae8e1..00000000 --- a/internal/crypto/cms/errors.go +++ /dev/null @@ -1,62 +0,0 @@ -package cms - -import "errors" - -// ErrExpectSignedData is returned if wrong content is provided when signed -// data is expected. -var ErrExpectSignedData = errors.New("cms: signed data expected") - -// ErrAttributeNotFound is returned if attribute is not found in a given set. -var ErrAttributeNotFound = errors.New("attribute not found") - -// Verification errors -var ( - ErrSignerNotFound = VerificationError{Message: "signer not found"} - ErrCertificateNotFound = VerificationError{Message: "certificate not found"} -) - -// SyntaxError indicates that the ASN.1 data is invalid. -type SyntaxError struct { - Message string - Detail error -} - -// Error returns error message. -func (e SyntaxError) Error() string { - msg := "cms: syntax error" - if e.Message != "" { - msg += ": " + e.Message - } - if e.Detail != nil { - msg += ": " + e.Detail.Error() - } - return msg -} - -// Unwrap returns the internal error. -func (e SyntaxError) Unwrap() error { - return e.Detail -} - -// VerificationError indicates verification failures. -type VerificationError struct { - Message string - Detail error -} - -// Error returns error message. -func (e VerificationError) Error() string { - msg := "cms: verification failure" - if e.Message != "" { - msg += ": " + e.Message - } - if e.Detail != nil { - msg += ": " + e.Detail.Error() - } - return msg -} - -// Unwrap returns the internal error. -func (e VerificationError) Unwrap() error { - return e.Detail -} diff --git a/internal/crypto/cms/signed.go b/internal/crypto/cms/signed.go deleted file mode 100644 index 1ca4b92e..00000000 --- a/internal/crypto/cms/signed.go +++ /dev/null @@ -1,238 +0,0 @@ -package cms - -import ( - "bytes" - "crypto" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/hex" - "time" - - "github.com/notaryproject/notation-core-go/internal/crypto/hashutil" - "github.com/notaryproject/notation-core-go/internal/crypto/oid" -) - -// ParsedSignedData is a parsed SignedData structure for golang friendly types. -type ParsedSignedData struct { - Content []byte - ContentType asn1.ObjectIdentifier - Certificates []*x509.Certificate - CRLs []pkix.CertificateList - Signers []SignerInfo -} - -// ParseSignedData parses ASN.1 DER-encoded SignedData structure to golang friendly types. -func ParseSignedData(data []byte) (*ParsedSignedData, error) { - var contentInfo ContentInfo - if _, err := asn1.Unmarshal(data, &contentInfo); err != nil { - return nil, SyntaxError{Message: "invalid content info", Detail: err} - } - if !oid.SignedData.Equal(contentInfo.ContentType) { - return nil, ErrExpectSignedData - } - - var signedData SignedData - if _, err := asn1.Unmarshal(contentInfo.Content.Bytes, &signedData); err != nil { - return nil, SyntaxError{Message: "invalid signed data", Detail: err} - } - certs, err := x509.ParseCertificates(signedData.Certificates.Bytes) - if err != nil { - return nil, SyntaxError{Message: "invalid signed data", Detail: err} - } - - return &ParsedSignedData{ - Content: signedData.EncapsulatedContentInfo.Content, - ContentType: signedData.EncapsulatedContentInfo.ContentType, - Certificates: certs, - CRLs: signedData.CRLs, - Signers: signedData.SignerInfos, - }, nil -} - -// Verify attempts to verify the content in the parsed signed data against the signer -// information. The `Intermediates` in the verify options will be ignored and -// re-contrusted using the certificates in the parsed signed data. -// If more than one signature is present, the successful validation of any signature -// implies that the content in the parsed signed data is valid. -// On successful verification, the list of signing certificates that successfully -// verify is returned. -// If all signatures fail to verify, the last error is returned. -// References: -// - RFC 5652 5 Signed-data Content Type -// - RFC 5652 5.4 Message Digest Calculation Process -// - RFC 5652 5.6 Signature Verification Process -// WARNING: this function doesn't do any revocation checking. -func (d *ParsedSignedData) Verify(opts x509.VerifyOptions) ([]*x509.Certificate, error) { - if len(d.Signers) == 0 { - return nil, ErrSignerNotFound - } - if len(d.Certificates) == 0 { - return nil, ErrCertificateNotFound - } - - intermediates := x509.NewCertPool() - for _, cert := range d.Certificates { - intermediates.AddCert(cert) - } - opts.Intermediates = intermediates - verifiedSignerMap := map[string]*x509.Certificate{} - var lastErr error - for _, signer := range d.Signers { - cert, err := d.verify(signer, opts) - if err != nil { - lastErr = err - continue - } - thumbprint, err := hashutil.ComputeHash(crypto.SHA256, cert.Raw) - if err != nil { - return nil, err - } - verifiedSignerMap[hex.EncodeToString(thumbprint)] = cert - } - if len(verifiedSignerMap) == 0 { - return nil, lastErr - } - - verifiedSigners := make([]*x509.Certificate, 0, len(verifiedSignerMap)) - for _, cert := range verifiedSignerMap { - verifiedSigners = append(verifiedSigners, cert) - } - return verifiedSigners, nil -} - -// verify verifies the trust in a top-down manner. -// References: -// - RFC 5652 5.4 Message Digest Calculation Process -// - RFC 5652 5.6 Signature Verification Process -func (d *ParsedSignedData) verify(signer SignerInfo, opts x509.VerifyOptions) (*x509.Certificate, error) { - // find signer certificate - cert := d.getCertificate(signer.SignerIdentifier) - if cert == nil { - return nil, ErrCertificateNotFound - } - - // verify signer certificate - if _, err := cert.Verify(opts); err != nil { - return cert, VerificationError{Detail: err} - } - - // verify signature - return cert, d.verifySignature(signer, cert) -} - -// verifySignature verifies the signature with a trusted certificate. -// References: -// - RFC 5652 5.4 Message Digest Calculation Process -// - RFC 5652 5.6 Signature Verification Process -func (d *ParsedSignedData) verifySignature(signer SignerInfo, cert *x509.Certificate) error { - // verify signature - algorithm := getSignatureAlgorithmFromOID( - signer.DigestAlgorithm.Algorithm, - signer.SignatureAlgorithm.Algorithm, - ) - if algorithm == x509.UnknownSignatureAlgorithm { - return VerificationError{Message: "unknown signature algorithm"} - } - signed := d.Content - if len(signer.SignedAttributes) > 0 { - encoded, err := asn1.MarshalWithParams(signer.SignedAttributes, "set") - if err != nil { - return VerificationError{Message: "invalid signed attributes", Detail: err} - } - signed = encoded - } - if err := cert.CheckSignature(algorithm, signed, signer.Signature); err != nil { - return VerificationError{Detail: err} - } - - // verify attributes if present - if len(signer.SignedAttributes) == 0 { - return nil - } - - var contentType asn1.ObjectIdentifier - if err := signer.SignedAttributes.TryGet(oid.ContentType, &contentType); err != nil { - return VerificationError{Message: "invalid content type", Detail: err} - } - if !d.ContentType.Equal(contentType) { - return VerificationError{Message: "mismatch content type"} - } - - var expectedDigest []byte - if err := signer.SignedAttributes.TryGet(oid.MessageDigest, &expectedDigest); err != nil { - return VerificationError{Message: "invalid message digest", Detail: err} - } - hash, ok := oid.ConvertToHash(signer.DigestAlgorithm.Algorithm) - if !ok { - return VerificationError{Message: "unsupported digest algorithm"} - } - actualDigest, err := hashutil.ComputeHash(hash, d.Content) - if err != nil { - return VerificationError{Message: "hash failure", Detail: err} - } - if !bytes.Equal(expectedDigest, actualDigest) { - return VerificationError{Message: "mismatch message digest"} - } - - // sanity check on signing time - var signingTime time.Time - if err := signer.SignedAttributes.TryGet(oid.SigningTime, &signingTime); err != nil { - if err == ErrAttributeNotFound { - return nil - } - return VerificationError{Message: "invalid signing time", Detail: err} - } - if signingTime.Before(cert.NotBefore) || signingTime.After(cert.NotAfter) { - return VerificationError{Message: "signature signed when cert is inactive"} - } - - return nil -} - -// getCertificate finds the certificate by issuer name and issuer-specific -// serial number. -// Reference: RFC 5652 5 Signed-data Content Type -func (d *ParsedSignedData) getCertificate(ref IssuerAndSerialNumber) *x509.Certificate { - for _, cert := range d.Certificates { - if bytes.Equal(cert.RawIssuer, ref.Issuer.FullBytes) && cert.SerialNumber.Cmp(ref.SerialNumber) == 0 { - return cert - } - } - return nil -} - -// getSignatureAlgorithmFromOID converts ASN.1 digest and signature algorithm identifiers -// to golang signature algorithms. -func getSignatureAlgorithmFromOID(digestAlg, sigAlg asn1.ObjectIdentifier) x509.SignatureAlgorithm { - switch { - case oid.RSA.Equal(sigAlg): - switch { - case oid.SHA1.Equal(digestAlg): - return x509.SHA1WithRSA - case oid.SHA256.Equal(digestAlg): - return x509.SHA256WithRSA - case oid.SHA384.Equal(digestAlg): - return x509.SHA384WithRSA - case oid.SHA512.Equal(digestAlg): - return x509.SHA512WithRSA - } - case oid.SHA1WithRSA.Equal(sigAlg): - return x509.SHA1WithRSA - case oid.SHA256WithRSA.Equal(sigAlg): - return x509.SHA256WithRSA - case oid.SHA384WithRSA.Equal(sigAlg): - return x509.SHA384WithRSA - case oid.SHA512WithRSA.Equal(sigAlg): - return x509.SHA512WithRSA - case oid.ECDSAWithSHA1.Equal(sigAlg): - return x509.ECDSAWithSHA1 - case oid.ECDSAWithSHA256.Equal(sigAlg): - return x509.ECDSAWithSHA256 - case oid.ECDSAWithSHA384.Equal(sigAlg): - return x509.ECDSAWithSHA384 - case oid.ECDSAWithSHA512.Equal(sigAlg): - return x509.ECDSAWithSHA512 - } - return x509.UnknownSignatureAlgorithm -} diff --git a/internal/crypto/cms/signed_test.go b/internal/crypto/cms/signed_test.go deleted file mode 100644 index fd4594e8..00000000 --- a/internal/crypto/cms/signed_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package cms - -import ( - "crypto/x509" - "os" - "reflect" - "testing" - "time" -) - -func TestVerifySignedData(t *testing.T) { - // parse signed data - sigBytes, err := os.ReadFile("testdata/TimeStampToken.p7s") - if err != nil { - t.Fatal("failed to read test signature:", err) - } - signed, err := ParseSignedData(sigBytes) - if err != nil { - t.Fatal("ParseSignedData() error =", err) - } - - // basic check on parsed signed data - if got := len(signed.Certificates); got != 4 { - t.Fatalf("len(Certificates) = %v, want %v", got, 4) - } - if got := len(signed.Signers); got != 1 { - t.Fatalf("len(Signers) = %v, want %v", got, 1) - } - - // verify with no root CAs and should fail - roots := x509.NewCertPool() - opts := x509.VerifyOptions{ - Roots: roots, - KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping}, - CurrentTime: time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC), - } - if _, err := signed.Verify(opts); err == nil { - t.Errorf("ParseSignedData.Verify() error = %v, wantErr %v", err, true) - } else if vErr, ok := err.(VerificationError); !ok { - t.Errorf("ParseSignedData.Verify() error = %v, want VerificationError", err) - } else if _, ok := vErr.Detail.(x509.UnknownAuthorityError); !ok { - t.Errorf("ParseSignedData.Verify() VerificationError.Detail = %v, want UnknownAuthorityError", err) - } - - // verify with proper root CA - rootCABytes, err := os.ReadFile("testdata/GlobalSignRootCA.crt") - if err != nil { - t.Fatal("failed to read root CA certificate:", err) - } - if ok := roots.AppendCertsFromPEM(rootCABytes); !ok { - t.Fatal("failed to load root CA certificate") - } - verifiedSigners, err := signed.Verify(opts) - if err != nil { - t.Fatal("ParseSignedData.Verify() error =", err) - } - if !reflect.DeepEqual(verifiedSigners, signed.Certificates[:1]) { - t.Fatalf("ParseSignedData.Verify() = %v, want %v", verifiedSigners, signed.Certificates[:1]) - } -} - -func TestVerifyCorruptedSignedData(t *testing.T) { - // parse signed data - sigBytes, err := os.ReadFile("testdata/TimeStampToken.p7s") - if err != nil { - t.Fatal("failed to read test signature:", err) - } - signed, err := ParseSignedData(sigBytes) - if err != nil { - t.Fatal("ParseSignedData() error =", err) - } - - // corrupt the content - signed.Content = []byte("corrupted data") - - // verify with no root CAs and should fail - roots := x509.NewCertPool() - rootCABytes, err := os.ReadFile("testdata/GlobalSignRootCA.crt") - if err != nil { - t.Fatal("failed to read root CA certificate:", err) - } - if ok := roots.AppendCertsFromPEM(rootCABytes); !ok { - t.Fatal("failed to load root CA certificate") - } - opts := x509.VerifyOptions{ - Roots: roots, - KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping}, - CurrentTime: time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC), - } - if _, err := signed.Verify(opts); err == nil { - t.Errorf("ParseSignedData.Verify() error = %v, wantErr %v", err, true) - } else if _, ok := err.(VerificationError); !ok { - t.Errorf("ParseSignedData.Verify() error = %v, want VerificationError", err) - } -} diff --git a/internal/crypto/cms/testdata/GlobalSignRootCA.crt b/internal/crypto/cms/testdata/GlobalSignRootCA.crt deleted file mode 100644 index 8afb2190..00000000 --- a/internal/crypto/cms/testdata/GlobalSignRootCA.crt +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 -MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 -RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT -gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm -KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd -QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ -XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw -DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o -LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU -RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp -jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK -6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX -mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs -Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH -WD9f ------END CERTIFICATE----- diff --git a/internal/crypto/cms/testdata/TimeStampToken.p7s b/internal/crypto/cms/testdata/TimeStampToken.p7s deleted file mode 100644 index c036aac2..00000000 Binary files a/internal/crypto/cms/testdata/TimeStampToken.p7s and /dev/null differ diff --git a/internal/crypto/hashutil/hash.go b/internal/crypto/hashutil/hash.go deleted file mode 100644 index 2c7f6af9..00000000 --- a/internal/crypto/hashutil/hash.go +++ /dev/null @@ -1,17 +0,0 @@ -// Package hashutil provides utilities for hash. -package hashutil - -import ( - "crypto" -) - -// ComputeHash computes the digest of the message with the given hash algorithm. -// Callers should check the availability of the hash algorithm before invoking. -func ComputeHash(hash crypto.Hash, message []byte) ([]byte, error) { - h := hash.New() - _, err := h.Write(message) - if err != nil { - return nil, err - } - return h.Sum(nil), nil -} diff --git a/internal/crypto/oid/hash.go b/internal/crypto/oid/hash.go deleted file mode 100644 index b4110717..00000000 --- a/internal/crypto/oid/hash.go +++ /dev/null @@ -1,25 +0,0 @@ -package oid - -import ( - "crypto" - "encoding/asn1" -) - -// ConvertToHash converts ASN.1 digest algorithm identifier to golang crypto hash -// if it is available. -func ConvertToHash(alg asn1.ObjectIdentifier) (crypto.Hash, bool) { - var hash crypto.Hash - switch { - case SHA1.Equal(alg): - hash = crypto.SHA1 - case SHA256.Equal(alg): - hash = crypto.SHA256 - case SHA384.Equal(alg): - hash = crypto.SHA384 - case SHA512.Equal(alg): - hash = crypto.SHA512 - default: - return hash, false - } - return hash, hash.Available() -} diff --git a/internal/crypto/oid/oid.go b/internal/crypto/oid/oid.go deleted file mode 100644 index d849648d..00000000 --- a/internal/crypto/oid/oid.go +++ /dev/null @@ -1,67 +0,0 @@ -// Package oid collects object identifiers for crypto algorithms. -package oid - -import "encoding/asn1" - -// OIDs for hash algorithms -var ( - // SHA1 (id-sha1) is defined in RFC 8017 B.1 Hash Functions - SHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} - - // SHA256 (id-sha256) is defined in RFC 8017 B.1 Hash Functions - SHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} - - // SHA384 (id-sha384) is defined in RFC 8017 B.1 Hash Functions - SHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} - - // SHA512 (id-sha512) is defined in RFC 8017 B.1 Hash Functions - SHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3} -) - -// OIDs for signature algorithms -var ( - // RSA is defined in RFC 8017 C ASN.1 Module - RSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} - - // SHA1WithRSA is defined in RFC 8017 C ASN.1 Module - SHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} - - // SHA256WithRSA is defined in RFC 8017 C ASN.1 Module - SHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} - - // SHA384WithRSA is defined in RFC 8017 C ASN.1 Module - SHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} - - // SHA512WithRSA is defined in RFC 8017 C ASN.1 Module - SHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} - - // ECDSAWithSHA1 is defined in ANSI X9.62 - ECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} - - // ECDSAWithSHA256 is defined in RFC 5758 3.2 ECDSA Signature Algorithm - ECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} - - // ECDSAWithSHA384 is defined in RFC 5758 3.2 ECDSA Signature Algorithm - ECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} - - // ECDSAWithSHA512 is defined in RFC 5758 3.2 ECDSA Signature Algorithm - ECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} -) - -// OIDs defined in RFC 5652 Cryptographic Message Syntax (CMS) -var ( - // SignedData (id-signedData) is defined in RFC 5652 5.1 SignedData Type - SignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2} - - // ContentType (id-ct-contentType) is defined in RFC 5652 3 General Syntax - ContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3} - - // MessageDigest (id-messageDigest) is defined in RFC 5652 11.2 Message Digest - MessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4} - - // SigningTime (id-signingTime) is defined in RFC 5652 11.3 Signing Time - SigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5} -) - -// TSTInfo (id-ct-TSTInfo) is defined in RFC 3161 2.4.2 Response Format -var TSTInfo = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 1, 4} diff --git a/internal/crypto/pki/pki.go b/internal/crypto/pki/pki.go deleted file mode 100644 index 9066b6c4..00000000 --- a/internal/crypto/pki/pki.go +++ /dev/null @@ -1,50 +0,0 @@ -// Package pki contains certificate management protocol structures -// defined in RFC 2510. -package pki - -import "encoding/asn1" - -// PKIStatus is defined in RFC 2510 3.2.3. -const ( - StatusGranted = 0 // you got exactly what you asked for - StatusGrantedWithMods = 1 // you got something like what you asked for - StatusRejection = 2 // you don't get it, more information elsewhere in the message - StatusWaiting = 3 // the request body part has not yet been processed, expect to hear more later - StatusRevocationWarning = 4 // this message contains a warning that a revocation is imminent - StatusRevocationNotification = 5 // notification that a revocation has occurred - StatusKeyUpdateWarning = 6 // update already done for the oldCertId specified in the key update request message -) - -// PKIFailureInfo is defined in RFC 2510 3.2.3 and RFC 3161 2.4.2. -const ( - FailureInfoBadAlg = 0 // unrecognized or unsupported Algorithm Identifier - FailureInfoBadMessageCheck = 1 // integrity check failed (e.g., signature did not verify) - FailureInfoBadRequest = 2 // transaction not permitted or supported - FailureInfoBadTime = 3 // messageTime was not sufficiently close to the system time, as defined by local policy - FailureInfoBadCertID = 4 // no certificate could be found matching the provided criteria - FailureInfoBadDataFormat = 5 // the data submitted has the wrong format - FailureInfoWrongAuthority = 6 // the authority indicated in the request is different from the one creating the response token - FailureInfoIncorrectData = 7 // the requester's data is incorrect (used for notary services) - FailureInfoMissingTimeStamp = 8 // the timestamp is missing but should be there (by policy) - FailureInfoBadPOP = 9 // the proof-of-possession failed - FailureInfoTimeNotAvailable = 14 // the TSA's time source is not available - FailureInfoUnacceptedPolicy = 15 // the requested TSA policy is not supported by the TSA. - FailureInfoUnacceptedExtension = 16 // the requested extension is not supported by the TSA. - FailureInfoAddInfoNotAvailable = 17 // the additional information requested could not be understood or is not available - FailureInfoSystemFailure = 25 // the request cannot be handled due to system failure -) - -// StatusInfo contains status codes and failure information for PKI messages. -// PKIStatusInfo ::= SEQUENCE { -// status PKIStatus, -// statusString PKIFreeText OPTIONAL, -// failInfo PKIFailureInfo OPTIONAL } -// PKIStatus ::= INTEGER -// PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String -// PKIFailureInfo ::= BIT STRING -// Reference: RFC 2510 3.2.3 Status codes and Failure Information for PKI messages. -type StatusInfo struct { - Status int - StatusString []string `asn1:"optional,utf8"` - FailInfo asn1.BitString `asn1:"optional"` -} diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go deleted file mode 100644 index 9c70eac1..00000000 --- a/internal/encoding/asn1/asn1.go +++ /dev/null @@ -1,54 +0,0 @@ -// Package asn1 decodes BER-encoded ASN.1 data structures and encodes in DER. -// Note: DER is a subset of BER. -// Reference: http://luca.ntop.org/Teaching/Appunti/asn1.html -package asn1 - -import ( - "bytes" - "encoding/asn1" -) - -// Common errors -var ( - ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} - ErrExpectConstructed = asn1.SyntaxError{Msg: "constructed value expected"} - ErrExpectPrimitive = asn1.SyntaxError{Msg: "primitive value expected"} - ErrUnsupportedLength = asn1.StructuralError{Msg: "length method not supported"} -) - -// Value represents an ASN.1 value. -type Value interface { - // Encode encodes the value to the value writer in DER. - Encode(ValueWriter) error - - // EncodedLen returns the length in bytes of the encoded data. - EncodedLen() int -} - -// Decode decodes BER-encoded ASN.1 data structures. -func Decode(r ValueReader) (Value, error) { - peekIdentifier, err := r.ReadByte() - if err != nil { - return nil, err - } - if err = r.UnreadByte(); err != nil { - return nil, err - } - if isPrimitive(peekIdentifier) { - return DecodePrimitive(r) - } - return DecodeConstructed(r) -} - -// ConvertToDER converts BER-encoded ASN.1 data structures to DER-encoded. -func ConvertToDER(ber []byte) ([]byte, error) { - v, err := Decode(bytes.NewReader(ber)) - if err != nil { - return nil, err - } - buf := bytes.NewBuffer(make([]byte, 0, v.EncodedLen())) - if err = v.Encode(buf); err != nil { - return nil, err - } - return buf.Bytes(), nil -} diff --git a/internal/encoding/asn1/asn1_test.go b/internal/encoding/asn1/asn1_test.go deleted file mode 100644 index c2fa90d1..00000000 --- a/internal/encoding/asn1/asn1_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package asn1 - -import ( - "encoding/asn1" - "reflect" - "testing" -) - -func TestConvertToDER(t *testing.T) { - type data struct { - Type asn1.ObjectIdentifier - Value []byte - } - - want := data{ - Type: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}, - Value: []byte{ - 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, - 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, - 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, - 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, - }, - } - - ber := []byte{ - // Constructed value - 0x30, - // Constructed value length - 0x2e, - - // Type identifier - 0x06, - // Type length - 0x09, - // Type content - 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, - - // Value identifier - 0x04, - // Value length in BER - 0x81, 0x20, - // Value content - 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, - 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, - 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, - 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, - } - - der, err := ConvertToDER(ber) - if err != nil { - t.Errorf("ConvertToDER() error = %v", err) - return - } - - var got data - rest, err := asn1.Unmarshal(der, &got) - if err != nil { - t.Errorf("Failed to decode converted data: %v", err) - return - } - if len(rest) > 0 { - t.Errorf("Unexpected rest data: %v", rest) - return - } - if !reflect.DeepEqual(got, want) { - t.Errorf("got = %v, want %v", got, want) - } -} diff --git a/internal/encoding/asn1/common.go b/internal/encoding/asn1/common.go deleted file mode 100644 index 9b2c3a68..00000000 --- a/internal/encoding/asn1/common.go +++ /dev/null @@ -1,116 +0,0 @@ -package asn1 - -import "io" - -// isPrimitive checks the primitive flag in the identifier. -// Returns true if the value is primitive. -func isPrimitive(identifier byte) bool { - return identifier&0x20 == 0 -} - -// encodedLengthSize gives the number of octets used for encoding the length. -func encodedLengthSize(length int) int { - if length < 0x80 { - return 1 - } - - lengthSize := 1 - for ; length > 0; lengthSize++ { - length >>= 8 - } - return lengthSize -} - -// encodeLength encodes length octets in DER. -func encodeLength(w io.ByteWriter, length int) error { - // DER restriction: short form must be used for length less than 128 - if length < 0x80 { - return w.WriteByte(byte(length)) - } - - // DER restriction: long form must be encoded in the minimum number of octets - lengthSize := encodedLengthSize(length) - err := w.WriteByte(0x80 | byte(lengthSize-1)) - if err != nil { - return err - } - for i := lengthSize - 1; i > 0; i-- { - if err = w.WriteByte(byte(length >> (8 * (i - 1)))); err != nil { - return err - } - } - return nil -} - -// decodeIdentifier decodes identifier octets. -func decodeIdentifier(r io.ByteReader) ([]byte, error) { - b, err := r.ReadByte() - if err != nil { - return nil, err - } - - // low-tag-number form - identifier := []byte{b} - - // high-tag-number form - if b&0x1f == 0x1f { - for { - b, err = r.ReadByte() - if err != nil { - if err == io.EOF { - return nil, ErrEarlyEOF - } - return nil, err - } - identifier = append(identifier, b) - if b&0x80 != 0 { - break - } - } - } - - return identifier, nil -} - -// decodeLength decodes length octets. -// Indefinite length is not supported -func decodeLength(r io.ByteReader) (int, error) { - b, err := r.ReadByte() - if err != nil { - if err == io.EOF { - return 0, ErrEarlyEOF - } - return 0, err - } - switch { - case b < 0x80: - // short form - return int(b), nil - case b == 0x80: - // Indefinite-length method is not supported. - return 0, ErrUnsupportedLength - } - - // long form - n := int(b & 0x7f) - if n > 4 { - // length must fit the memory space of the int type. - return 0, ErrUnsupportedLength - } - var length int - for i := 0; i < n; i++ { - b, err = r.ReadByte() - if err != nil { - if err == io.EOF { - return 0, ErrEarlyEOF - } - return 0, err - } - length = (length << 8) | int(b) - } - if length < 0 { - // double check in case that length is over 31 bits. - return 0, ErrUnsupportedLength - } - return length, nil -} diff --git a/internal/encoding/asn1/common_test.go b/internal/encoding/asn1/common_test.go deleted file mode 100644 index 83c24ce5..00000000 --- a/internal/encoding/asn1/common_test.go +++ /dev/null @@ -1,172 +0,0 @@ -package asn1 - -import ( - "bytes" - "testing" -) - -func Test_encodeLength(t *testing.T) { - tests := []struct { - name string - length int - want []byte - wantErr bool - }{ - { - name: "zero length", - length: 0, - want: []byte{0x00}, - }, - { - name: "short form", - length: 42, - want: []byte{0x2a}, - }, - { - name: "short form in max", - length: 127, - want: []byte{0x7f}, - }, - { - name: "long form in min", - length: 128, - want: []byte{0x81, 0x80}, - }, - { - name: "long form", - length: 1234567890, - want: []byte{0x84, 0x49, 0x96, 0x02, 0xd2}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - buf := bytes.NewBuffer(nil) - if err := encodeLength(buf, tt.length); (err != nil) != tt.wantErr { - t.Errorf("encodeLength() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got := buf.Bytes(); !bytes.Equal(got, tt.want) { - t.Errorf("encoded length = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_decodeIdentifier(t *testing.T) { - tests := []struct { - name string - encoded []byte - want []byte - wantErr bool - }{ - { - name: "empty identifier", - wantErr: true, - }, - { - name: "low-tag-number form", - encoded: []byte{0x0b}, - want: []byte{0x0b}, - }, - { - name: "no extra read in low-tag-number form", - encoded: []byte{0x0b, 0x42}, - want: []byte{0x0b}, - }, - { - name: "high-tag-number form", - encoded: []byte{0x1f, 0x17, 0xdf}, - want: []byte{0x1f, 0x17, 0xdf}, // tag: 0x012345 - }, - { - name: "no extra read in high-tag-number form", - encoded: []byte{0x1f, 0x17, 0xdf, 0x42}, - want: []byte{0x1f, 0x17, 0xdf}, // tag: 0x012345 - }, - { - name: "high-tag-number form (no termination)", - encoded: []byte{0x1f, 0x17, 0x5f}, - wantErr: true, - }, - { - name: "high-tag-number form (EOF)", - encoded: []byte{0x1f, 0x17}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := bytes.NewReader(tt.encoded) - got, err := decodeIdentifier(r) - if (err != nil) != tt.wantErr { - t.Errorf("decodeIdentifier() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !bytes.Equal(got, tt.want) { - t.Errorf("decodeIdentifier() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_decodeLength(t *testing.T) { - tests := []struct { - name string - encoded []byte - want int - wantErr bool - }{ - { - name: "empty length", - wantErr: true, - }, - { - name: "short form", - encoded: []byte{0x2a}, - want: 42, - }, - { - name: "no extra read in short form", - encoded: []byte{0x2a, 0x42}, - want: 42, - }, - { - name: "long form", - encoded: []byte{0x84, 0x49, 0x96, 0x02, 0xd2}, - want: 1234567890, - }, - { - name: "long form in BER", - encoded: []byte{0x81, 0x2a}, - want: 42, - }, - { - name: "no extra read in long form", - encoded: []byte{0x84, 0x49, 0x96, 0x02, 0xd2, 0x42}, - want: 1234567890, - }, - { - name: "long form (indefinite)", - encoded: []byte{0x80, 0x42, 0x00, 0x00}, - wantErr: true, - }, - { - name: "long form (EOF)", - encoded: []byte{0x84, 0x49, 0x96, 0x02}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := bytes.NewReader(tt.encoded) - got, err := decodeLength(r) - if (err != nil) != tt.wantErr { - t.Errorf("decodeLength() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("decodeLength() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go deleted file mode 100644 index cf847fec..00000000 --- a/internal/encoding/asn1/constructed.go +++ /dev/null @@ -1,68 +0,0 @@ -package asn1 - -import "io" - -// ConstructedValue represents a value in constructed encoding. -type ConstructedValue struct { - identifier []byte - length int - members []Value -} - -// Encode encodes the constructed value to the value writer in DER. -func (v ConstructedValue) Encode(w ValueWriter) error { - _, err := w.Write(v.identifier) - if err != nil { - return err - } - if err = encodeLength(w, v.length); err != nil { - return err - } - for _, value := range v.members { - if err = value.Encode(w); err != nil { - return err - } - } - return nil -} - -// EncodedLen returns the length in bytes of the encoded data. -func (v ConstructedValue) EncodedLen() int { - return len(v.identifier) + encodedLengthSize(v.length) + v.length -} - -// DecodeConstructed decodes a constructed value in BER. -func DecodeConstructed(r ValueReader) (Value, error) { - identifier, err := decodeIdentifier(r) - if err != nil { - return nil, err - } - if isPrimitive(identifier[0]) { - return nil, ErrExpectConstructed - } - expectedLength, err := decodeLength(r) - if err != nil { - return nil, err - } - - var members []Value - encodedLength := 0 - r = LimitValueReader(r, int64(expectedLength)) - for { - value, err := Decode(r) - if err != nil { - if err == io.EOF { - break - } - return nil, err - } - members = append(members, value) - encodedLength += value.EncodedLen() - } - - return ConstructedValue{ - identifier: identifier, - length: encodedLength, - members: members, - }, nil -} diff --git a/internal/encoding/asn1/io.go b/internal/encoding/asn1/io.go deleted file mode 100644 index 03b5c00b..00000000 --- a/internal/encoding/asn1/io.go +++ /dev/null @@ -1,51 +0,0 @@ -package asn1 - -import "io" - -// ValueReader is the interface for reading a value. -type ValueReader interface { - io.Reader - io.ByteScanner -} - -// ValueWriter is the interface for writing a value. -type ValueWriter interface { - io.Writer - io.ByteWriter -} - -// limitedValueReader limits the amount of data returned. -type limitedValueReader struct { - io.LimitedReader - S io.ByteScanner -} - -// LimitValueReader returns a ValueReader, which limits the amount of data returned. -func LimitValueReader(r ValueReader, n int64) ValueReader { - return &limitedValueReader{ - LimitedReader: io.LimitedReader{ - R: r, - N: n, - }, - S: r, - } -} - -func (l *limitedValueReader) ReadByte() (c byte, err error) { - if l.N <= 0 { - return 0, io.EOF - } - c, err = l.S.ReadByte() - if err == nil { - l.N-- - } - return -} - -func (l *limitedValueReader) UnreadByte() (err error) { - err = l.S.UnreadByte() - if err == nil { - l.N++ - } - return -} diff --git a/internal/encoding/asn1/primitive.go b/internal/encoding/asn1/primitive.go deleted file mode 100644 index e51f87e2..00000000 --- a/internal/encoding/asn1/primitive.go +++ /dev/null @@ -1,55 +0,0 @@ -package asn1 - -import "io" - -// PrimitiveValue represents a value in primitive encoding. -type PrimitiveValue struct { - identifier []byte - content []byte -} - -// Encode encodes the primitive value to the value writer in DER. -func (v PrimitiveValue) Encode(w ValueWriter) error { - _, err := w.Write(v.identifier) - if err != nil { - return err - } - if err = encodeLength(w, len(v.content)); err != nil { - return err - } - _, err = w.Write(v.content) - return err -} - -// EncodedLen returns the length in bytes of the encoded data. -func (v PrimitiveValue) EncodedLen() int { - return len(v.identifier) + encodedLengthSize(len(v.content)) + len(v.content) -} - -// DecodePrimitive decodes a primitive value in BER. -func DecodePrimitive(r ValueReader) (Value, error) { - identifier, err := decodeIdentifier(r) - if err != nil { - return nil, err - } - if !isPrimitive(identifier[0]) { - return nil, ErrExpectPrimitive - } - length, err := decodeLength(r) - if err != nil { - return nil, err - } - content := make([]byte, length) - _, err = io.ReadFull(r, content) - if err != nil { - if err == io.EOF { - return nil, ErrEarlyEOF - } - return nil, err - } - - return PrimitiveValue{ - identifier: identifier, - content: content, - }, nil -} diff --git a/timestamp/errors.go b/timestamp/errors.go deleted file mode 100644 index 1ccdd4b2..00000000 --- a/timestamp/errors.go +++ /dev/null @@ -1,10 +0,0 @@ -package timestamp - -// MalformedRequestError is used when timestamping request is malformed. -type MalformedRequestError struct { - msg string -} - -func (e MalformedRequestError) Error() string { - return e.msg -} diff --git a/timestamp/http.go b/timestamp/http.go deleted file mode 100644 index 62078762..00000000 --- a/timestamp/http.go +++ /dev/null @@ -1,78 +0,0 @@ -package timestamp - -import ( - "bytes" - "context" - "fmt" - "io" - "net/http" - "net/url" -) - -// maxBodyLength specifies the max content can be received from the possibly malicious -// remote server. -// The legnth of a regular TSA response with certificates is usually less than 10 KiB. -const maxBodyLength = 1 * 1024 * 1024 // 1 MiB - -// httpTimestamper is a HTTP-based timestamper. -type httpTimestamper struct { - rt http.RoundTripper - endpoint string -} - -// NewHTTPTimestamper creates a HTTP-based timestamper with the endpoint provided by the TSA. -// http.DefaultTransport is used if nil RoundTripper is passed. -func NewHTTPTimestamper(rt http.RoundTripper, endpoint string) (Timestamper, error) { - if rt == nil { - rt = http.DefaultTransport - } - if _, err := url.Parse(endpoint); err != nil { - return nil, err - } - return &httpTimestamper{ - rt: rt, - endpoint: endpoint, - }, nil -} - -// Timestamp sends the request to the remote TSA server for timestamping. -// Reference: RFC 3161 3.4 Time-Stamp Protocol via HTTP -func (ts *httpTimestamper) Timestamp(ctx context.Context, req *Request) (*Response, error) { - // prepare for http request - reqBytes, err := req.MarshalBinary() - if err != nil { - return nil, err - } - hReq, err := http.NewRequestWithContext(ctx, http.MethodPost, ts.endpoint, bytes.NewReader(reqBytes)) - if err != nil { - return nil, err - } - hReq.Header.Set("Content-Type", "application/timestamp-query") - - // send the request to the remote TSA server - hResp, err := ts.rt.RoundTrip(hReq) - if err != nil { - return nil, err - } - defer hResp.Body.Close() - - // verify HTTP response - if hResp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status: %s", hResp.Status) - } - if contentType := hResp.Header.Get("Content-Type"); contentType != "application/timestamp-reply" { - return nil, fmt.Errorf("unexpected response content type: %s", contentType) - } - - // read response - body := io.LimitReader(hResp.Body, maxBodyLength) - respBytes, err := io.ReadAll(body) - if err != nil { - return nil, err - } - var resp Response - if err := resp.UnmarshalBinary(respBytes); err != nil { - return nil, err - } - return &resp, nil -} diff --git a/timestamp/http_test.go b/timestamp/http_test.go deleted file mode 100644 index d0fcf3cc..00000000 --- a/timestamp/http_test.go +++ /dev/null @@ -1,252 +0,0 @@ -package timestamp - -import ( - "bytes" - "context" - "crypto" - "crypto/x509" - "encoding/asn1" - "io" - "net/http" - "net/http/httptest" - "os" - "reflect" - "testing" - "time" - - "github.com/notaryproject/notation-core-go/internal/crypto/hashutil" - "github.com/notaryproject/notation-core-go/internal/crypto/pki" -) - -var testRequest = []byte{ - // Request - 0x30, 0x37, - - // Version - 0x02, 0x01, 0x01, - - // MessageImprint - 0x30, 0x2f, - - // MessageImprint.HashAlgorithm - 0x30, 0x0b, - - // MessageImprint.HashAlgorithm.Algorithm - 0x06, 0x09, - 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, - - // MessageImprint.HashedMessage - 0x04, 0x20, - 0x83, 0x26, 0xf4, 0x70, 0x9d, 0x40, 0x1d, 0xfa, 0xbf, 0xa7, 0x83, 0x02, 0xfb, 0x1c, 0xde, 0xa0, - 0xf1, 0x80, 0x48, 0xa4, 0x40, 0x40, 0xc2, 0x12, 0xbd, 0x8e, 0x28, 0xda, 0x6b, 0xc6, 0x51, 0xc7, - - // CertReq - 0x01, 0x01, 0xff, -} - -func TestHTTPTimestampGranted(t *testing.T) { - // setup test server - testResp, err := os.ReadFile("testdata/granted.tsq") - if err != nil { - t.Fatal("failed to read test response:", err) - } - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - const wantContentType = "application/timestamp-query" - if got := r.Header.Get("Content-Type"); got != wantContentType { - t.Fatalf("TimeStampRequest.ContentType = %v, want %v", err, wantContentType) - } - if got, err := io.ReadAll(r.Body); err != nil { - t.Fatalf("TimeStampRequest.Body read error = %v", err) - } else if !bytes.Equal(got, testRequest) { - t.Fatalf("TimeStampRequest.Body = %v, want %v", got, testRequest) - } - - // write reply - w.Header().Set("Content-Type", "application/timestamp-reply") - if _, err := w.Write(testResp); err != nil { - t.Error("failed to write response:", err) - } - })) - defer ts.Close() - - // do timestamp - tsa, err := NewHTTPTimestamper(nil, ts.URL) - if err != nil { - t.Fatalf("NewHTTPTimestamper() error = %v", err) - } - message := []byte("notation") - req, err := NewRequestFromContent(message, crypto.SHA256) - if err != nil { - t.Fatalf("NewRequestFromContent() error = %v", err) - } - ctx := context.Background() - resp, err := tsa.Timestamp(ctx, req) - if err != nil { - t.Fatalf("httpTimestamper.Timestamp() error = %v", err) - } - wantStatus := pki.StatusGranted - if got := resp.Status.Status; got != wantStatus { - t.Fatalf("Response.Status = %v, want %v", got, wantStatus) - } - - // verify timestamp token - token, err := resp.SignedToken() - if err != nil { - t.Fatalf("Response.SignedToken() error = %v", err) - } - roots := x509.NewCertPool() - rootCABytes, err := os.ReadFile("testdata/GlobalSignRootCA.crt") - if err != nil { - t.Fatal("failed to read root CA certificate:", err) - } - if ok := roots.AppendCertsFromPEM(rootCABytes); !ok { - t.Fatal("failed to load root CA certificate") - } - opts := x509.VerifyOptions{ - Roots: roots, - CurrentTime: time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC), - } - certs, err := token.Verify(opts) - if err != nil { - t.Fatal("SignedToken.Verify() error =", err) - } - if got := len(certs); got != 1 { - t.Fatalf("SignedToken.Verify() len([]*x509.Certificate) = %v, want %v", got, 1) - } - certThumbprint, err := hashutil.ComputeHash(crypto.SHA256, certs[0].Raw) - if err != nil { - t.Fatal("failed to compute certificate thumbprint:", err) - } - wantCertThumbprint := []byte{ - 0x13, 0xd6, 0xe9, 0xc4, 0x20, 0xff, 0x6d, 0x4e, 0x27, 0x54, 0x72, 0x8c, 0x68, 0xe7, 0x78, 0x82, - 0x65, 0x64, 0x67, 0xdb, 0x9a, 0x19, 0x0f, 0x81, 0x65, 0x97, 0xf6, 0x7f, 0xb6, 0xcc, 0xc6, 0xf9, - } - if !bytes.Equal(certThumbprint, wantCertThumbprint) { - t.Fatalf("SignedToken.Verify() = %v, want %v", certThumbprint, wantCertThumbprint) - } - info, err := token.Info() - if err != nil { - t.Fatal("SignedToken.Info() error =", err) - } - if err := info.VerifyContent(message); err != nil { - t.Errorf("TSTInfo.Verify() error = %v", err) - } - timestamp, accuracy := info.Timestamp() - wantTimestamp := time.Date(2021, 9, 18, 11, 54, 34, 0, time.UTC) - if timestamp != wantTimestamp { - t.Errorf("TSTInfo.Timestamp() Timestamp = %v, want %v", timestamp, wantTimestamp) - } - wantAccuracy := time.Second - if accuracy != wantAccuracy { - t.Errorf("TSTInfo.Timestamp() Accuracy = %v, want %v", accuracy, wantAccuracy) - } -} - -func TestHTTPTimestampRejection(t *testing.T) { - // setup test server - testResp, err := os.ReadFile("testdata/rejection.tsq") - if err != nil { - t.Fatal("failed to read test response:", err) - } - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - const wantContentType = "application/timestamp-query" - if got := r.Header.Get("Content-Type"); got != wantContentType { - t.Fatalf("TimeStampRequest.ContentType = %v, want %v", err, wantContentType) - } - if got, err := io.ReadAll(r.Body); err != nil { - t.Fatalf("TimeStampRequest.Body read error = %v", err) - } else if !bytes.Equal(got, testRequest) { - t.Fatalf("TimeStampRequest.Body = %v, want %v", got, testRequest) - } - - // write reply - w.Header().Set("Content-Type", "application/timestamp-reply") - if _, err := w.Write(testResp); err != nil { - t.Error("failed to write response:", err) - } - })) - defer ts.Close() - - // do timestamp - tsa, err := NewHTTPTimestamper(nil, ts.URL) - if err != nil { - t.Fatalf("NewHTTPTimestamper() error = %v", err) - } - message := []byte("notation") - req, err := NewRequestFromContent(message, crypto.SHA256) - if err != nil { - t.Fatalf("NewRequestFromContent() error = %v", err) - } - ctx := context.Background() - resp, err := tsa.Timestamp(ctx, req) - if err != nil { - t.Fatalf("httpTimestamper.Timestamp() error = %v", err) - } - wantStatus := pki.StatusRejection - if got := resp.Status.Status; got != wantStatus { - t.Fatalf("Response.Status = %v, want %v", got, wantStatus) - } - wantStatusString := []string{"request contains unknown algorithm"} - if got := resp.Status.StatusString; !reflect.DeepEqual(got, wantStatusString) { - t.Fatalf("Response.StatusString = %v, want %v", got, wantStatusString) - } - wantFailInfo := asn1.BitString{ - Bytes: []byte{0x80}, - BitLength: 1, - } - if got := resp.Status.FailInfo; !reflect.DeepEqual(got, wantFailInfo) { - t.Fatalf("Response.FailInfo = %v, want %v", got, wantFailInfo) - } -} - -func TestHTTPTimestampBadEndpoint(t *testing.T) { - // setup test server - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // write reply - w.Header().Set("Content-Type", "application/json; charset=utf-8") - if _, err := w.Write([]byte("{}")); err != nil { - t.Error("failed to write response:", err) - } - })) - defer ts.Close() - - // do timestamp - tsa, err := NewHTTPTimestamper(nil, ts.URL) - if err != nil { - t.Fatalf("NewHTTPTimestamper() error = %v", err) - } - message := []byte("notation") - req, err := NewRequestFromContent(message, crypto.SHA256) - if err != nil { - t.Fatalf("NewRequestFromContent() error = %v", err) - } - ctx := context.Background() - _, err = tsa.Timestamp(ctx, req) - if err == nil { - t.Fatalf("httpTimestamper.Timestamp() error = %v, wantErr %v", err, true) - } -} - -func TestHTTPTimestampEndpointNotFound(t *testing.T) { - // setup test server - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - })) - defer ts.Close() - - // do timestamp - tsa, err := NewHTTPTimestamper(nil, ts.URL) - if err != nil { - t.Fatalf("NewHTTPTimestamper() error = %v", err) - } - message := []byte("notation") - req, err := NewRequestFromContent(message, crypto.SHA256) - if err != nil { - t.Fatalf("NewRequestFromContent() error = %v", err) - } - ctx := context.Background() - _, err = tsa.Timestamp(ctx, req) - if err == nil { - t.Fatalf("httpTimestamper.Timestamp() error = %v, wantErr %v", err, true) - } -} diff --git a/timestamp/request.go b/timestamp/request.go deleted file mode 100644 index 1f4d926f..00000000 --- a/timestamp/request.go +++ /dev/null @@ -1,112 +0,0 @@ -package timestamp - -import ( - "crypto" - "crypto/x509/pkix" - "encoding/asn1" - "errors" - "fmt" - "math/big" - - "github.com/notaryproject/notation-core-go/internal/crypto/hashutil" - "github.com/notaryproject/notation-core-go/internal/crypto/oid" -) - -// MessageImprint contains the hash of the datum to be time-stamped. -// MessageImprint ::= SEQUENCE { -// hashAlgorithm AlgorithmIdentifier, -// hashedMessage OCTET STRING } -type MessageImprint struct { - HashAlgorithm pkix.AlgorithmIdentifier - HashedMessage []byte -} - -// Request is a time-stamping request. -// TimeStampReq ::= SEQUENCE { -// version INTEGER { v1(1) }, -// messageImprint MessageImprint, -// reqPolicy TSAPolicyID OPTIONAL, -// nonce INTEGER OPTIONAL, -// certReq BOOLEAN DEFAULT FALSE, -// extensions [0] IMPLICIT Extensions OPTIONAL } -type Request struct { - Version int // fixed to 1 as defined in RFC 3161 2.4.1 Request Format - MessageImprint MessageImprint - ReqPolicy asn1.ObjectIdentifier `asn1:"optional"` - Nonce *big.Int `asn1:"optional"` - CertReq bool `asn1:"optional,default:false"` - Extensions []pkix.Extension `asn1:"optional,tag:0"` -} - -// NewRequest creates a request based on the given digest and hash algorithm. -func NewRequest(digest []byte, alg crypto.Hash) (*Request, error) { - err := validate(digest, alg) - if err != nil { - return nil, err - } - - hashAlg, err := getOID(alg) - if err != nil { - return nil, err - } - return &Request{ - Version: 1, - MessageImprint: MessageImprint{ - HashAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: hashAlg, - }, - HashedMessage: digest, - }, - CertReq: true, - }, nil -} - -// NewRequestFromContent creates a request based on the given data and hash algorithm. -func NewRequestFromContent(content []byte, alg crypto.Hash) (*Request, error) { - digest, err := hashutil.ComputeHash(alg, content) - if err != nil { - return nil, err - } - - return NewRequest(digest, alg) -} - -// MarshalBinary encodes the request to binary form. -// This method implements encoding.BinaryMarshaler -func (r *Request) MarshalBinary() ([]byte, error) { - if r == nil { - return nil, errors.New("nil request") - } - return asn1.Marshal(*r) -} - -// UnmarshalBinary decodes the request from binary form. -// This method implements encoding.BinaryUnmarshaler -func (r *Request) UnmarshalBinary(data []byte) error { - _, err := asn1.Unmarshal(data, r) - return err -} - -// getOID returns corresponding ASN.1 OID for the given Hash algorithm. -func getOID(alg crypto.Hash) (asn1.ObjectIdentifier, error) { - switch alg { - case crypto.SHA256: - return oid.SHA256, nil - case crypto.SHA384: - return oid.SHA384, nil - case crypto.SHA512: - return oid.SHA512, nil - } - return nil, MalformedRequestError{msg: fmt.Sprintf("unsupported hashing algorithm: %s", alg)} -} - -func validate(digest []byte, alg crypto.Hash) error { - if !(alg == crypto.SHA256 || alg == crypto.SHA384 || alg == crypto.SHA512) { - return MalformedRequestError{msg: fmt.Sprintf("unsupported hashing algorithm: %s", alg)} - } - - if len(digest) != alg.Size() { - return MalformedRequestError{msg: fmt.Sprintf("digest is of incorrect size: %d", len(digest))} - } - return nil -} \ No newline at end of file diff --git a/timestamp/response.go b/timestamp/response.go deleted file mode 100644 index ca6c538f..00000000 --- a/timestamp/response.go +++ /dev/null @@ -1,44 +0,0 @@ -package timestamp - -import ( - "encoding/asn1" - "errors" - - "github.com/notaryproject/notation-core-go/internal/crypto/pki" -) - -// Response is a time-stamping response. -// TimeStampResp ::= SEQUENCE { -// status PKIStatusInfo, -// timeStampToken TimeStampToken OPTIONAL } -type Response struct { - Status pki.StatusInfo - TimeStampToken asn1.RawValue `asn1:"optional"` -} - -// MarshalBinary encodes the response to binary form. -// This method implements encoding.BinaryMarshaler -func (r *Response) MarshalBinary() ([]byte, error) { - if r == nil { - return nil, errors.New("nil response") - } - return asn1.Marshal(r) -} - -// UnmarshalBinary decodes the response from binary form. -// This method implements encoding.BinaryUnmarshaler -func (r *Response) UnmarshalBinary(data []byte) error { - _, err := asn1.Unmarshal(data, r) - return err -} - -// TokenBytes returns the bytes of the timestamp token. -func (r *Response) TokenBytes() []byte { - return r.TimeStampToken.FullBytes -} - -// SignedToken returns the timestamp token with signatures. -// Callers should invoke Verify to verify the content before comsumption. -func (r *Response) SignedToken() (*SignedToken, error) { - return ParseSignedToken(r.TokenBytes()) -} diff --git a/timestamp/testdata/GlobalSignRootCA.crt b/timestamp/testdata/GlobalSignRootCA.crt deleted file mode 100644 index 8afb2190..00000000 --- a/timestamp/testdata/GlobalSignRootCA.crt +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 -MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 -RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT -gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm -KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd -QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ -XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw -DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o -LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU -RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp -jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK -6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX -mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs -Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH -WD9f ------END CERTIFICATE----- diff --git a/timestamp/testdata/granted.tsq b/timestamp/testdata/granted.tsq deleted file mode 100644 index b62ea6f8..00000000 Binary files a/timestamp/testdata/granted.tsq and /dev/null differ diff --git a/timestamp/testdata/rejection.tsq b/timestamp/testdata/rejection.tsq deleted file mode 100644 index 4c44dae9..00000000 --- a/timestamp/testdata/rejection.tsq +++ /dev/null @@ -1 +0,0 @@ -0/0-0$ "request contains unknown algorithm� \ No newline at end of file diff --git a/timestamp/timestamp.go b/timestamp/timestamp.go deleted file mode 100644 index f8cd7118..00000000 --- a/timestamp/timestamp.go +++ /dev/null @@ -1,11 +0,0 @@ -// Package timestamp generates timestamping requests to TSA servers, -// and fetches the responses according to RFC 3161. -package timestamp - -import "context" - -// Timestamper stamps the time. -type Timestamper interface { - // Timestamp stamps the time with the given request. - Timestamp(context.Context, *Request) (*Response, error) -} diff --git a/timestamp/timestamptest/tsa.go b/timestamp/timestamptest/tsa.go deleted file mode 100644 index ae842179..00000000 --- a/timestamp/timestamptest/tsa.go +++ /dev/null @@ -1,269 +0,0 @@ -// Package timestamptest provides utilities for timestamp testing -package timestamptest - -import ( - "context" - "crypto" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "math" - "math/big" - "time" - - "github.com/notaryproject/notation-core-go/internal/crypto/cms" - "github.com/notaryproject/notation-core-go/internal/crypto/hashutil" - "github.com/notaryproject/notation-core-go/internal/crypto/oid" - "github.com/notaryproject/notation-core-go/internal/crypto/pki" - "github.com/notaryproject/notation-core-go/timestamp" -) - -// responseRejection is a general response for request rejection. -var responseRejection = ×tamp.Response{ - Status: pki.StatusInfo{ - Status: pki.StatusRejection, - }, -} - -// TSA is a Timestamping Authority for testing purpose. -type TSA struct { - // key is the TSA signing key. - key *rsa.PrivateKey - - // cert is the self-signed certificate by the TSA signing key. - cert *x509.Certificate - - // NowFunc provides the current time. time.Now() is used if nil. - NowFunc func() time.Time -} - -// NewTSA creates a TSA with random credentials. -func NewTSA() (*TSA, error) { - // generate key - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, err - } - - // generate certificate - serialNumber, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) - if err != nil { - return nil, err - } - now := time.Now() - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - CommonName: "timestamp test", - }, - NotBefore: now, - NotAfter: now.Add(365 * 24 * time.Hour), // 1 year - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping}, - BasicConstraintsValid: true, - } - certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, key.Public(), key) - if err != nil { - return nil, err - } - cert, err := x509.ParseCertificate(certBytes) - if err != nil { - return nil, err - } - - return &TSA{ - key: key, - cert: cert, - }, nil -} - -// Certificate returns the certificate used by the server. -func (tsa *TSA) Certificate() *x509.Certificate { - return tsa.cert -} - -// Timestamp stamps the time with the given request. -func (tsa *TSA) Timestamp(_ context.Context, req *timestamp.Request) (*timestamp.Response, error) { - // validate request - if req.Version != 1 { - return responseRejection, nil - } - hash, ok := oid.ConvertToHash(req.MessageImprint.HashAlgorithm.Algorithm) - if !ok { - return responseRejection, nil - } - if hashedMessage := req.MessageImprint.HashedMessage; len(hashedMessage) != hash.Size() { - return responseRejection, nil - } - - // generate token info - policy := asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 2} // time-stamp-policies - switch hash { - case crypto.SHA1: - policy = append(policy, 2) - case crypto.SHA256, crypto.SHA384, crypto.SHA512: - policy = append(policy, 3) - default: - return responseRejection, nil - } - infoBytes, err := tsa.generateTokenInfo(req, policy) - if err != nil { - return nil, err - } - - // generate signed data - signed, err := tsa.generateSignedData(infoBytes, req.CertReq) - if err != nil { - return nil, err - } - content, err := convertToRawASN1(signed, "explicit,tag:0") - if err != nil { - return nil, err - } - - // generate content info - contentInfo := cms.ContentInfo{ - ContentType: oid.SignedData, - Content: content, - } - token, err := convertToRawASN1(contentInfo, "") - if err != nil { - return nil, err - } - - // generate response - return ×tamp.Response{ - Status: pki.StatusInfo{ - Status: pki.StatusGranted, - }, - TimeStampToken: token, - }, nil -} - -// generateTokenInfo generate timestamp token info. -func (tsa *TSA) generateTokenInfo(req *timestamp.Request, policy asn1.ObjectIdentifier) ([]byte, error) { - serialNumber, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) - if err != nil { - return nil, err - } - nowFunc := tsa.NowFunc - if nowFunc == nil { - nowFunc = time.Now - } - info := timestamp.TSTInfo{ - Version: 1, - Policy: policy, - MessageImprint: req.MessageImprint, - SerialNumber: serialNumber, - GenTime: nowFunc().UTC().Truncate(time.Second), - Accuracy: timestamp.Accuracy{ - Seconds: 1, - }, - } - return asn1.Marshal(info) -} - -// generateSignedData generate signed data according to -func (tsa *TSA) generateSignedData(infoBytes []byte, requestCert bool) (cms.SignedData, error) { - var issuer asn1.RawValue - _, err := asn1.Unmarshal(tsa.cert.RawIssuer, &issuer) - if err != nil { - return cms.SignedData{}, err - } - contentType, err := convertToRawASN1([]interface{}{oid.TSTInfo}, "set") - if err != nil { - return cms.SignedData{}, err - } - infoDigest, err := hashutil.ComputeHash(crypto.SHA256, infoBytes) - if err != nil { - return cms.SignedData{}, err - } - messageDigest, err := convertToRawASN1([]interface{}{infoDigest}, "set") - if err != nil { - return cms.SignedData{}, err - } - signingTime, err := convertToRawASN1([]interface{}{time.Now().UTC()}, "set") - if err != nil { - return cms.SignedData{}, err - } - signed := cms.SignedData{ - Version: 3, - DigestAlgorithmIdentifiers: []pkix.AlgorithmIdentifier{ - { - Algorithm: oid.SHA256, - }, - }, - EncapsulatedContentInfo: cms.EncapsulatedContentInfo{ - ContentType: oid.TSTInfo, - Content: infoBytes, - }, - SignerInfos: []cms.SignerInfo{ - { - Version: 1, - SignerIdentifier: cms.IssuerAndSerialNumber{ - Issuer: issuer, - SerialNumber: tsa.cert.SerialNumber, - }, - DigestAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: oid.SHA256, - }, - SignedAttributes: cms.Attributes{ - { - Type: oid.ContentType, - Values: contentType, - }, - { - Type: oid.MessageDigest, - Values: messageDigest, - }, - { - Type: oid.SigningTime, - Values: signingTime, - }, - }, - SignatureAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: oid.SHA256WithRSA, - }, - }, - }, - } - if requestCert { - certs, err := convertToRawASN1(tsa.cert.Raw, "tag:0") - if err != nil { - return cms.SignedData{}, err - } - signed.Certificates = certs - } - - // sign data - signer := &signed.SignerInfos[0] - encodedAttributes, err := asn1.MarshalWithParams(signer.SignedAttributes, "set") - if err != nil { - return cms.SignedData{}, err - } - hashedAttributes, err := hashutil.ComputeHash(crypto.SHA256, encodedAttributes) - if err != nil { - return cms.SignedData{}, err - } - signer.Signature, err = rsa.SignPKCS1v15(rand.Reader, tsa.key, crypto.SHA256, hashedAttributes) - if err != nil { - return cms.SignedData{}, err - } - return signed, nil -} - -// convertToRawASN1 convert any data ASN.1 data structure to asn1.RawValue. -func convertToRawASN1(val interface{}, params string) (asn1.RawValue, error) { - b, err := asn1.MarshalWithParams(val, params) - if err != nil { - return asn1.NullRawValue, err - } - var raw asn1.RawValue - _, err = asn1.UnmarshalWithParams(b, &raw, params) - if err != nil { - return asn1.NullRawValue, err - } - return raw, nil -} diff --git a/timestamp/timestamptest/tsa_test.go b/timestamp/timestamptest/tsa_test.go deleted file mode 100644 index 07d4fea1..00000000 --- a/timestamp/timestamptest/tsa_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package timestamptest - -import ( - "context" - "crypto" - "crypto/x509" - "testing" - "time" - - "github.com/notaryproject/notation-core-go/internal/crypto/oid" - "github.com/notaryproject/notation-core-go/internal/crypto/pki" - "github.com/notaryproject/notation-core-go/timestamp" -) - -func TestTSATimestampGranted(t *testing.T) { - // prepare TSA - now := time.Date(2021, 9, 18, 11, 54, 34, 0, time.UTC) - tsa, err := NewTSA() - if err != nil { - t.Fatalf("NewTSA() error = %v", err) - } - tsa.NowFunc = func() time.Time { - return now - } - - // do timestamp - message := []byte("notation") - req, err := timestamp.NewRequestFromContent(message, crypto.SHA256) - if err != nil { - t.Fatalf("NewRequestFromContent() error = %v", err) - } - ctx := context.Background() - resp, err := tsa.Timestamp(ctx, req) - if err != nil { - t.Fatalf("TSA.Timestamp() error = %v", err) - } - wantStatus := pki.StatusGranted - if got := resp.Status.Status; got != wantStatus { - t.Fatalf("Response.Status = %v, want %v", got, wantStatus) - } - - // verify timestamp token - token, err := resp.SignedToken() - if err != nil { - t.Fatalf("Response.SignedToken() error = %v", err) - } - roots := x509.NewCertPool() - roots.AddCert(tsa.Certificate()) - opts := x509.VerifyOptions{ - Roots: roots, - } - if _, err := token.Verify(opts); err != nil { - t.Fatal("SignedToken.Verify() error =", err) - } - info, err := token.Info() - if err != nil { - t.Fatal("SignedToken.Info() error =", err) - } - if err := info.VerifyContent(message); err != nil { - t.Errorf("TSTInfo.Verify() error = %v", err) - } - ts, accuracy := info.Timestamp() - wantTimestamp := now - if ts != wantTimestamp { - t.Errorf("TSTInfo.Timestamp() Timestamp = %v, want %v", ts, wantTimestamp) - } - wantAccuracy := time.Second - if accuracy != wantAccuracy { - t.Errorf("TSTInfo.Timestamp() Accuracy = %v, want %v", accuracy, wantAccuracy) - } -} - -func TestTSATimestampRejection(t *testing.T) { - // prepare TSA - tsa, err := NewTSA() - if err != nil { - t.Fatalf("NewTSA() error = %v", err) - } - - // do timestamp - message := []byte("notation") - req, err := timestamp.NewRequestFromContent(message, crypto.SHA256) - if err != nil { - t.Fatalf("NewRequestFromContent() error = %v", err) - } - req.MessageImprint.HashAlgorithm.Algorithm = oid.SHA1WithRSA // set bad algorithm - ctx := context.Background() - resp, err := tsa.Timestamp(ctx, req) - if err != nil { - t.Fatalf("TSA.Timestamp() error = %v", err) - } - wantStatus := pki.StatusRejection - if got := resp.Status.Status; got != wantStatus { - t.Fatalf("Response.Status = %v, want %v", got, wantStatus) - } -} diff --git a/timestamp/token.go b/timestamp/token.go deleted file mode 100644 index 5e4ec19d..00000000 --- a/timestamp/token.go +++ /dev/null @@ -1,138 +0,0 @@ -package timestamp - -import ( - "bytes" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "errors" - "fmt" - "math/big" - "time" - - "github.com/notaryproject/notation-core-go/internal/crypto/cms" - "github.com/notaryproject/notation-core-go/internal/crypto/hashutil" - "github.com/notaryproject/notation-core-go/internal/crypto/oid" - asn1util "github.com/notaryproject/notation-core-go/internal/encoding/asn1" -) - -// SignedToken is a parsed timestamp token with signatures. -type SignedToken cms.ParsedSignedData - -// ParseSignedToken parses ASN.1 BER-encoded structure to SignedToken -// without verification. -// Callers should invoke Verify to verify the content before comsumption. -func ParseSignedToken(data []byte) (*SignedToken, error) { - data, err := asn1util.ConvertToDER(data) - if err != nil { - return nil, err - } - signed, err := cms.ParseSignedData(data) - if err != nil { - return nil, err - } - if !oid.TSTInfo.Equal(signed.ContentType) { - return nil, fmt.Errorf("unexpected content type: %v", signed.ContentType) - } - return (*SignedToken)(signed), nil -} - -// Verify verifies the signed token as CMS SignedData. -// An empty list of KeyUsages in VerifyOptions implies ExtKeyUsageTimeStamping. -func (t *SignedToken) Verify(opts x509.VerifyOptions) ([]*x509.Certificate, error) { - if len(opts.KeyUsages) == 0 { - opts.KeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping} - } - signed := (*cms.ParsedSignedData)(t) - certs, err := signed.Verify(opts) - if err != nil { - return nil, err - } - - // RFC 3161 2.3: The corresponding certificate MUST contain only one instance of - // the extended key usage field extension. - verifiedCerts := make([]*x509.Certificate, 0, len(certs)) - for _, cert := range certs { - if len(cert.ExtKeyUsage) == 1 && len(cert.UnknownExtKeyUsage) == 0 { - verifiedCerts = append(verifiedCerts, cert) - } - } - if len(verifiedCerts) == 0 { - return nil, errors.New("unexpected number of extended key usages") - } - return verifiedCerts, nil -} - -// Info returns the timestamping information. -func (t *SignedToken) Info() (*TSTInfo, error) { - var info TSTInfo - if _, err := asn1.Unmarshal(t.Content, &info); err != nil { - return nil, err - } - return &info, nil -} - -// Accuracy ::= SEQUENCE { -// seconds INTEGER OPTIONAL, -// millis [0] INTEGER (1..999) OPTIONAL, -// micros [1] INTEGER (1..999) OPTIONAL } -type Accuracy struct { - Seconds int `asn1:"optional"` - Milliseconds int `asn1:"optional,tag:0"` - Microseconds int `asn1:"optional,tag:1"` -} - -// TSTInfo ::= SEQUENCE { -// version INTEGER { v1(1) }, -// policy TSAPolicyId, -// messageImprint MessageImprint, -// serialNumber INTEGER, -// genTime GeneralizedTime, -// accuracy Accuracy OPTIONAL, -// ordering BOOLEAN DEFAULT FALSE, -// nonce INTEGER OPTIONAL, -// tsa [0] GeneralName OPTIONAL, -// extensions [1] IMPLICIT Extensions OPTIONAL } -type TSTInfo struct { - Version int // fixed to 1 as defined in RFC 3161 2.4.2 Response Format - Policy asn1.ObjectIdentifier - MessageImprint MessageImprint - SerialNumber *big.Int - GenTime time.Time `asn1:"generalized"` - Accuracy Accuracy `asn1:"optional"` - Ordering bool `asn1:"optional,default:false"` - Nonce *big.Int `asn1:"optional"` - TSA asn1.RawValue `asn1:"optional,tag:0"` - Extensions []pkix.Extension `asn1:"optional,tag:1"` -} - -// VerifyContent verifies the message against the timestamp token information. -func (tst *TSTInfo) VerifyContent(message []byte) error { - hashAlg := tst.MessageImprint.HashAlgorithm.Algorithm - hash, ok := oid.ConvertToHash(hashAlg) - if !ok { - return fmt.Errorf("unrecognized hash algorithm: %v", hashAlg) - } - messageDigest, err := hashutil.ComputeHash(hash, message) - if err != nil { - return err - } - - return tst.Verify(messageDigest) -} - -// Verify verifies the message digest against the timestamp token information. -func (tst *TSTInfo) Verify(messageDigest []byte) error { - if !bytes.Equal(tst.MessageImprint.HashedMessage, messageDigest) { - return errors.New("mismatch message digest") - } - return nil -} - -// Timestamp returns the timestamp by TSA and its accuracy. -func (tst *TSTInfo) Timestamp() (time.Time, time.Duration) { - accuracy := time.Duration(tst.Accuracy.Seconds)*time.Second + - time.Duration(tst.Accuracy.Milliseconds)*time.Millisecond + - time.Duration(tst.Accuracy.Microseconds)*time.Microsecond - return tst.GenTime, accuracy -}