pax_global_header00006660000000000000000000000064145403621270014516gustar00rootroot0000000000000052 comment=7e48227210692f7ca0a359d9131cff180f8a54a5 go-securesystemslib-0.8.0/000077500000000000000000000000001454036212700155335ustar00rootroot00000000000000go-securesystemslib-0.8.0/.github/000077500000000000000000000000001454036212700170735ustar00rootroot00000000000000go-securesystemslib-0.8.0/.github/dependabot.yml000066400000000000000000000007601454036212700217260ustar00rootroot00000000000000version: 2 updates: # Monitor Go dependencies - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" time: "10:00" commit-message: prefix: "chore" include: "scope" open-pull-requests-limit: 10 # Monitor Github Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" time: "10:00" commit-message: prefix: "chore" include: "scope" open-pull-requests-limit: 10 go-securesystemslib-0.8.0/.github/workflows/000077500000000000000000000000001454036212700211305ustar00rootroot00000000000000go-securesystemslib-0.8.0/.github/workflows/build.yml000066400000000000000000000016141454036212700227540ustar00rootroot00000000000000on: [push, pull_request] name: build jobs: test: strategy: matrix: go-version: [1.18.x, 1.19.x, 1.20.x, 1.21.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - name: Install Go uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Format Unix if: runner.os == 'Linux' run: test -z $(go fmt ./...) - name: Test run: go test -v ./... staticcheck: name: "Run staticcheck" runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dominikh/staticcheck-action@ba605356b4b29a60e87ab9404b712f3461e566dc with: version: "2022.1" go-securesystemslib-0.8.0/LICENSE000066400000000000000000000021011454036212700165320ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2021 NYU Secure Systems Lab Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-securesystemslib-0.8.0/README.md000066400000000000000000000001571454036212700170150ustar00rootroot00000000000000A library that provides cryptographic and general-purpose functions for Go Secure Systems Lab projects at NYU. go-securesystemslib-0.8.0/cjson/000077500000000000000000000000001454036212700166475ustar00rootroot00000000000000go-securesystemslib-0.8.0/cjson/canonicaljson.go000066400000000000000000000101321454036212700220140ustar00rootroot00000000000000package cjson import ( "bytes" "encoding/json" "errors" "fmt" "reflect" "sort" "strings" ) /* encodeCanonicalString is a helper function to canonicalize the passed string according to the OLPC canonical JSON specification for strings (see http://wiki.laptop.org/go/Canonical_JSON). String canonicalization consists of escaping backslashes ("\") and double quotes (") and wrapping the resulting string in double quotes ("). */ func encodeCanonicalString(s string) string { // Escape backslashes s = strings.ReplaceAll(s, "\\", "\\\\") // Escape double quotes s = strings.ReplaceAll(s, "\"", "\\\"") // Wrap with double quotes return fmt.Sprintf("\"%s\"", s) } /* encodeCanonical is a helper function to recursively canonicalize the passed object according to the OLPC canonical JSON specification (see http://wiki.laptop.org/go/Canonical_JSON) and write it to the passed *bytes.Buffer. If canonicalization fails it returns an error. */ func encodeCanonical(obj interface{}, result *strings.Builder) (err error) { switch objAsserted := obj.(type) { case string: result.WriteString(encodeCanonicalString(objAsserted)) case bool: if objAsserted { result.WriteString("true") } else { result.WriteString("false") } // The wrapping `EncodeCanonical` function decodes the passed json data with // `decoder.UseNumber` so that any numeric value is stored as `json.Number` // (instead of the default `float64`). This allows us to assert that it is a // non-floating point number, which are the only numbers allowed by the used // canonicalization specification. case json.Number: if _, err := objAsserted.Int64(); err != nil { panic(fmt.Sprintf("Can't canonicalize floating point number '%s'", objAsserted)) } result.WriteString(objAsserted.String()) case nil: result.WriteString("null") // Canonicalize slice case []interface{}: result.WriteString("[") for i, val := range objAsserted { if err := encodeCanonical(val, result); err != nil { return err } if i < (len(objAsserted) - 1) { result.WriteString(",") } } result.WriteString("]") case map[string]interface{}: result.WriteString("{") // Make a list of keys var mapKeys []string for key := range objAsserted { mapKeys = append(mapKeys, key) } // Sort keys sort.Strings(mapKeys) // Canonicalize map for i, key := range mapKeys { if err := encodeCanonical(key, result); err != nil { return err } result.WriteString(":") if err := encodeCanonical(objAsserted[key], result); err != nil { return err } if i < (len(mapKeys) - 1) { result.WriteString(",") } i++ } result.WriteString("}") default: // We recover in a deferred function defined above panic(fmt.Sprintf("Can't canonicalize '%s' of type '%s'", objAsserted, reflect.TypeOf(objAsserted))) } return nil } /* EncodeCanonical JSON canonicalizes the passed object and returns it as a byte slice. It uses the OLPC canonical JSON specification (see http://wiki.laptop.org/go/Canonical_JSON). If canonicalization fails the byte slice is nil and the second return value contains the error. */ func EncodeCanonical(obj interface{}) (out []byte, err error) { // We use panic if an error occurs and recover in a deferred function, // which is always called before returning. // There we set the error that is returned eventually. defer func() { if r := recover(); r != nil { err = errors.New(r.(string)) } }() // FIXME: Terrible hack to turn the passed struct into a map, converting // the struct's variable names to the json key names defined in the struct data, err := json.Marshal(obj) if err != nil { return nil, err } var jsonMap interface{} dec := json.NewDecoder(bytes.NewReader(data)) dec.UseNumber() if err := dec.Decode(&jsonMap); err != nil { return nil, err } // Create a buffer and write the canonicalized JSON bytes to it var result strings.Builder // Allocate output result buffer with the input size. result.Grow(len(data)) // Recursively encode the jsonmap if err := encodeCanonical(jsonMap, &result); err != nil { return nil, err } return []byte(result.String()), nil } go-securesystemslib-0.8.0/cjson/canonicaljson_test.go000066400000000000000000000314251454036212700230630ustar00rootroot00000000000000package cjson import ( "encoding/json" "fmt" "strings" "testing" "github.com/stretchr/testify/assert" ) type keyVal struct { Private string `json:"private"` Public string `json:"public"` Certificate string `json:"certificate,omitempty"` } type key struct { KeyID string `json:"keyid"` KeyIDHashAlgorithms []string `json:"keyid_hash_algorithms"` KeyType string `json:"keytype"` KeyVal keyVal `json:"keyval"` Scheme string `json:"scheme"` } func TestEncodeCanonical(t *testing.T) { objects := []interface{}{ key{}, key{ KeyVal: keyVal{ Private: "priv", Public: "pub", }, KeyIDHashAlgorithms: []string{"hash"}, KeyID: "id", KeyType: "type", Scheme: "scheme", }, map[string]interface{}{ "true": true, "false": false, "nil": nil, "int": 3, "int2": float64(42), "string": `\"`, }, key{ KeyVal: keyVal{ Certificate: "cert", Private: "priv", Public: "pub", }, KeyIDHashAlgorithms: []string{"hash"}, KeyID: "id", KeyType: "type", Scheme: "scheme", }, json.RawMessage(`{"_type":"targets","spec_version":"1.0","version":0,"expires":"0001-01-01T00:00:00Z","targets":{},"custom":{"test":true}}`), } expectedResult := []string{ `{"keyid":"","keyid_hash_algorithms":null,"keytype":"","keyval":{"private":"","public":""},"scheme":""}`, `{"keyid":"id","keyid_hash_algorithms":["hash"],"keytype":"type","keyval":{"private":"priv","public":"pub"},"scheme":"scheme"}`, `{"false":false,"int":3,"int2":42,"nil":null,"string":"\\\"","true":true}`, `{"keyid":"id","keyid_hash_algorithms":["hash"],"keytype":"type","keyval":{"certificate":"cert","private":"priv","public":"pub"},"scheme":"scheme"}`, `{"_type":"targets","custom":{"test":true},"expires":"0001-01-01T00:00:00Z","spec_version":"1.0","targets":{},"version":0}`, } for i := 0; i < len(objects); i++ { result, err := EncodeCanonical(objects[i]) if string(result) != expectedResult[i] || err != nil { t.Errorf("EncodeCanonical returned (%s, %s), expected (%s, nil)", result, err, expectedResult[i]) } } } func TestEncodeCanonicalErr(t *testing.T) { objects := []interface{}{ map[string]interface{}{"float": 3.14159265359}, TestEncodeCanonical, } errPart := []string{ "Can't canonicalize floating point number", "unsupported type: func(", } for i := 0; i < len(objects); i++ { result, err := EncodeCanonical(objects[i]) if err == nil || !strings.Contains(err.Error(), errPart[i]) { t.Errorf("EncodeCanonical returned (%s, %s), expected '%s' error", result, err, errPart[i]) } } } func TestEncodeCanonicalHelper(t *testing.T) { defer func() { if r := recover(); r == nil { t.Error("encodeCanonical did not panic as expected") } }() objects := []interface{}{ TestEncodeCanonicalHelper, []interface{}{TestEncodeCanonicalHelper}, } for i := 0; i < len(objects); i++ { var result strings.Builder err := encodeCanonical(objects[i], &result) assert.Nil(t, err) } } // ----------------------------------------------------------------------------- // Size 146b var smallFixture = json.RawMessage(`{"keyid":"id","keyid_hash_algorithms":["hash"],"keytype":"type","keyval":{"certificate":"cert","private":"priv","public":"pub"},"scheme":"scheme"}`) // Response from Github Webhook. Size: 2.7kb var mediumFixture = json.RawMessage(`{"after":"1481a2de7b2a7d02428ad93446ab166be7793fbb","before":"17c497ccc7cca9c2f735aa07e9e3813060ce9a6a","commits":[{"added":[],"author":{"email":"lolwut@noway.biz","name":"Garen Torikian","username":"octokitty"},"committer":{"email":"lolwut@noway.biz","name":"Garen Torikian","username":"octokitty"},"distinct":true,"id":"c441029cf673f84c8b7db52d0a5944ee5c52ff89","message":"Test","modified":["README.md"],"removed":[],"timestamp":"2013-02-22T13:50:07-08:00","url":"https://github.com/octokitty/testing/commit/c441029cf673f84c8b7db52d0a5944ee5c52ff89"},{"added":[],"author":{"email":"lolwut@noway.biz","name":"Garen Torikian","username":"octokitty"},"committer":{"email":"lolwut@noway.biz","name":"Garen Torikian","username":"octokitty"},"distinct":true,"id":"36c5f2243ed24de58284a96f2a643bed8c028658","message":"This is me testing the windows client.","modified":["README.md"],"removed":[],"timestamp":"2013-02-22T14:07:13-08:00","url":"https://github.com/octokitty/testing/commit/36c5f2243ed24de58284a96f2a643bed8c028658"},{"added":["words/madame-bovary.txt"],"author":{"email":"lolwut@noway.biz","name":"Garen Torikian","username":"octokitty"},"committer":{"email":"lolwut@noway.biz","name":"Garen Torikian","username":"octokitty"},"distinct":true,"id":"1481a2de7b2a7d02428ad93446ab166be7793fbb","message":"Rename madame-bovary.txt to words/madame-bovary.txt","modified":[],"removed":["madame-bovary.txt"],"timestamp":"2013-03-12T08:14:29-07:00","url":"https://github.com/octokitty/testing/commit/1481a2de7b2a7d02428ad93446ab166be7793fbb"}],"compare":"https://github.com/octokitty/testing/compare/17c497ccc7cc...1481a2de7b2a","created":false,"deleted":false,"forced":false,"head_commit":{"added":["words/madame-bovary.txt"],"author":{"email":"lolwut@noway.biz","name":"Garen Torikian","username":"octokitty"},"committer":{"email":"lolwut@noway.biz","name":"Garen Torikian","username":"octokitty"},"distinct":true,"id":"1481a2de7b2a7d02428ad93446ab166be7793fbb","message":"Rename madame-bovary.txt to words/madame-bovary.txt","modified":[],"removed":["madame-bovary.txt"],"timestamp":"2013-03-12T08:14:29-07:00","url":"https://github.com/octokitty/testing/commit/1481a2de7b2a7d02428ad93446ab166be7793fbb"},"pusher":{"email":"lolwut@noway.biz","name":"Garen Torikian"},"ref":"refs/heads/master","repository":{"created_at":1332977768,"description":"","fork":false,"forks":0,"has_downloads":true,"has_issues":true,"has_wiki":true,"homepage":"","id":3860742,"language":"Ruby","master_branch":"master","name":"testing","open_issues":2,"owner":{"email":"lolwut@noway.biz","name":"octokitty"},"private":false,"pushed_at":1363295520,"size":2156,"stargazers":1,"url":"https://github.com/octokitty/testing","watchers":1}}`) // Response from Facebook. Size: 6.5kb var largeFixture = json.RawMessage(`{"stat":"ok","profile":{"providerName":"Facebook","identifier":"http://www.facebook.com/profile.php?id=100BlahBlah7767","verifiedEmail":"2013-11-22 21:01:09.601637 +0000","preferredUsername":"RpxDoc","displayName":"Rpx Doc","name":{"formatted":"Rpx Doc","givenName":"Rpx","familyName":"Doc"},"email":"rpxdoc@yahoo.com","url":"http://www.facebook.com/rpx.doc","photo":"https://graph.facebook.com/100BlahBlah7767/picture?type=large","utcOffset":"-08:00","address":{"formatted":"Portland, Oregon","type":"currentLocation"},"birthday":"1994-05-19","gender":"female"},"merged_poco":{"id":"http://www.facebook.com/profile.php?id=100BlahBlah7767","displayName":"Rpx Doc","preferredUsername":"RpxDoc","gender":"female","aboutMe":"i test stuff","profileUrl":"http://www.facebook.com/rpx.doc","relationshipStatus":"Widowed","religion":"u0627u0644u0627u0633u0644u0627u0645","status":"set_status timestamp test: Wed, 17 Oct 12 21:36:34 +0000","currentLocation":{"formatted":"Portland, Oregon"},"politicalViews":"Bude mu00edt toto vejce vu00edce fanouu0161ku016f neu017e Jiu0159u00ed Paroubek ?","name":{"formatted":"Rpx Doc","givenName":"Rpx","familyName":"Doc"},"updated":"2012-09-13T00:44:03.000Z","birthday":"1994-05-19","utcOffset":"-08:00","emails":[{"value":"rpxdoc@yahoo.com","type":"other","primary":true}],"languagesSpoken":["Pig Latin"],"urls":[{"value":"http://www.facebook.com/rpx.doc","type":"profile"},{"value":"http://www.facepalm.org","type":"other"},{"value":"http://foo.com","type":"other"}],"addresses":[{"formatted":"Portland, Oregon","type":"currentLocation"},{"formatted":"Luxembourg","type":"hometown"}],"books":["Dr. Seuss' The Cat in the Hat","Good Omens"],"movies":["Gigli","Big Trouble in Little China"],"music":["My favorite playlist","Country music","Western"],"tvShows":["Voltran","American Idol","ThunderCats","Seinfeld"],"quotes":["I'm getting ENOSPACE writing to /dev/null."],"interests":["Justin Bieber"],"sports":["Frolf","Underwater hockey"],"heroes":["Donkey","Shrek"],"activities":["Underwater basket weaving"],"photos":[{"value":"https://graph.facebook.com/100BlahBlah7767/picture?type=small","type":"other"},{"value":"https://graph.facebook.com/100BlahBlah7767/picture?type=large","type":"other","primary":true},{"value":"https://graph.facebook.com/100BlahBlah7767/picture?type=square","type":"other"},{"value":"https://graph.facebook.com/100BlahBlah7767/picture?type=normal","type":"other"}],"organizations":[{"name":"Janrain","title":"Tester","type":"job","startDate":"2007-05","description":"I am."},{"name":"Janrain","title":"a wee tester","type":"job","startDate":"0000-00","description":"something clever"},{"name":"Janrain","title":"To Test","type":"job","startDate":"2009-01","endDate":"2009-02"},{"name":"Janrain","title":"Testing Monkey","type":"job","startDate":"2006-02","endDate":"2005-02","description":"I was."},{"name":"School Of Rock","type":"High School"},{"name":"Hogwarts School of Witchcraft and Wizardry","type":"College"}]},"friends":["http://www.facebook.com/profile.php?id=1234BlahBlah254","http://www.facebook.com/profile.php?id=1234BlahBlah434","http://www.facebook.com/profile.php?id=1234BlahBlah662"],"provider":{"facebook":{"albums":[{"id":"326BlahBlah6808","name":"Untitled Album","link":"http://www.facebook.com/album.php?fbid=1234BlahBlah808&id=100BlahBlah7767&aid=78839","privacy":"custom","type":"normal"},{"id":"326BlahBlah0163","name":"Timeline Photos","link":"http://www.facebook.com/album.php?fbid=326BlahBlah0163&id=100BlahBlah7767&aid=78838","privacy":"everyone","type":"wall"},{"id":"322BlahBlah7306","name":"Cover Photos","link":"http://www.facebook.com/album.php?fbid=322BlahBlah7306&id=100BlahBlah7767&aid=77860","privacy":"everyone","type":"normal"},{"id":"322BlahBlah1017","name":"Untitled Album","link":"http://www.facebook.com/album.php?fbid=322BlahBlah1017&id=100BlahBlah7767&aid=77858","privacy":"custom","type":"normal"},{"id":"102BlahBlah3100","name":"Profile Pictures","link":"http://www.facebook.com/album.php?fbid=102BlahBlah3100&id=100BlahBlah7767&aid=4035","privacy":"everyone","type":"profile"}],"games":[{"name":"Axis & Allies","category":"Interest","id":"124BlahBlah6166"},{"name":"UNO","category":"Games/toys","id":"123BlahBlah6939"}],"groups":[{"name":"Test group","id":"123BlahBlah2994"},{"name":"Exploratory Group","id":"123BlahBlah7259"}],"videos":[{"id":"350BlahBlah1104","description":"a super awesome movie!!!","picture":"http://example.com/hvthumb-ak-snc6/245400_350BlahBlah1061_350BlahBlah1104_2773_417_t.jpg","icon":"http://example.com/rsrc.php/v2/yD/r/DggBlahz4tO.gif","embed_html":"","source":"http://example.com/cfs-ak-ash4/v/34xyz3/743/350BlahBlah1104_8269.mp4?oh=3f74c5a67BlahBlah33eb2d7f72d0dc1&oe=5080CF78&__gda__=1350674533_97d8568b1a07387e4cee5d02d87262b9"},{"id":"123BlahBlah7762","description":"what what!","picture":"http://example.com/hvthumb-ak-ash4/245318_350BlahBlah4397_350BlahBlah7762_37327_361_t.jpg","icon":"http://example.com/rsrc.php/v2/yD/r/DggBlahz4tO.gif","embed_html":"","source":"http://example.com/cfs-ak-snc7/v/610161/125/350BlahBlah7762_24214.mp4?oh=3f527BlahBlahBlahBlah8dd9c665ba0&oe=5080F026&__gda__=1350Blah08_f3da7404BlahBlah6f886b3fce52ea4a"}]}},"limited_data":"false","accessCredentials":{"accessToken":"AAAFArLqJQIBlahBlaha0rCdu9m5d5fBlahBlahFKYWpp401H9LGf5rQasuZAzrMyoZA9J45FDSZACLyNCXkAZAgpDFr0hG8NBkb8CccXXuQZDZD","uid":"100BlahBlah7767","expires":1355690751,"scopes":"email,publish_stream,user_birthday,user_location,user_hometown,user_relationships,user_interests,user_about_me,user_photos,user_work_history,friends_hometown,friends_interests,friends_relationships,friends_photos,friends_location,friends_about_me,friends_birthday,friends_work_history,read_stream,read_insights,create_event,rsvp_event,sms,read_requests,read_mailbox,read_friendlists,xmpp_login,ads_management,manage_pages,user_checkins,friends_checkins,publish_checkins,user_online_presence,friends_online_presence,user_education_history,friends_education_history,user_religion_politics,friends_religion_politics,user_likes,manage_notifications,friends_actions.music,user_actions.music,user_activities,friends_likes,friends_relationship_details,publish_actions,friends_events,user_notes,friends_notes,friends_questions,friends_videos,user_website,friends_status,friends_activities,manage_friendlists,user_events,user_groups,friends_groups,user_questions,user_videos,friends_website","type":"Facebook"}}`) func BenchmarkEncodeCanonical(b *testing.B) { var table = []struct { input json.RawMessage }{ {input: smallFixture}, {input: mediumFixture}, {input: largeFixture}, } for _, v := range table { b.Run(fmt.Sprintf("input_size_%d", len(v.input)), func(b *testing.B) { for i := 0; i < b.N; i++ { EncodeCanonical(v.input) } }) } } go-securesystemslib-0.8.0/dsse/000077500000000000000000000000001454036212700164715ustar00rootroot00000000000000go-securesystemslib-0.8.0/dsse/envelope.go000066400000000000000000000033031454036212700206340ustar00rootroot00000000000000package dsse import ( "encoding/base64" "fmt" ) /* Envelope captures an envelope as described by the DSSE specification. See here: https://github.com/secure-systems-lab/dsse/blob/master/envelope.md */ type Envelope struct { PayloadType string `json:"payloadType"` Payload string `json:"payload"` Signatures []Signature `json:"signatures"` } /* DecodeB64Payload returns the serialized body, decoded from the envelope's payload field. A flexible decoder is used, first trying standard base64, then URL-encoded base64. */ func (e *Envelope) DecodeB64Payload() ([]byte, error) { return b64Decode(e.Payload) } /* Signature represents a generic in-toto signature that contains the identifier of the key which was used to create the signature. The used signature scheme has to be agreed upon by the signer and verifer out of band. The signature is a base64 encoding of the raw bytes from the signature algorithm. */ type Signature struct { KeyID string `json:"keyid"` Sig string `json:"sig"` } /* PAE implementes the DSSE Pre-Authentic Encoding https://github.com/secure-systems-lab/dsse/blob/master/protocol.md#signature-definition */ func PAE(payloadType string, payload []byte) []byte { return []byte(fmt.Sprintf("DSSEv1 %d %s %d %s", len(payloadType), payloadType, len(payload), payload)) } /* Both standard and url encoding are allowed: https://github.com/secure-systems-lab/dsse/blob/master/envelope.md */ func b64Decode(s string) ([]byte, error) { b, err := base64.StdEncoding.DecodeString(s) if err != nil { b, err = base64.URLEncoding.DecodeString(s) if err != nil { return nil, fmt.Errorf("unable to base64 decode payload (is payload in the right format?)") } } return b, nil } go-securesystemslib-0.8.0/dsse/sign.go000066400000000000000000000036631454036212700177700ustar00rootroot00000000000000/* Package dsse implements the Dead Simple Signing Envelope (DSSE) https://github.com/secure-systems-lab/dsse */ package dsse import ( "context" "encoding/base64" "errors" ) // ErrNoSigners indicates that no signer was provided. var ErrNoSigners = errors.New("no signers provided") // EnvelopeSigner creates signed Envelopes. type EnvelopeSigner struct { providers []Signer } /* NewEnvelopeSigner creates an EnvelopeSigner that uses 1+ Signer algorithms to sign the data. */ func NewEnvelopeSigner(p ...Signer) (*EnvelopeSigner, error) { var providers []Signer for _, s := range p { if s != nil { providers = append(providers, s) } } if len(providers) == 0 { return nil, ErrNoSigners } return &EnvelopeSigner{ providers: providers, }, nil } /* NewMultiEnvelopeSigner creates an EnvelopeSigner that uses 1+ Signer algorithms to sign the data. The threshold parameter is legacy and is ignored. Deprecated: This function simply calls NewEnvelopeSigner, and that function should be preferred. */ func NewMultiEnvelopeSigner(threshold int, p ...Signer) (*EnvelopeSigner, error) { return NewEnvelopeSigner(p...) } /* SignPayload signs a payload and payload type according to DSSE. Returned is an envelope as defined here: https://github.com/secure-systems-lab/dsse/blob/master/envelope.md One signature will be added for each Signer in the EnvelopeSigner. */ func (es *EnvelopeSigner) SignPayload(ctx context.Context, payloadType string, body []byte) (*Envelope, error) { var e = Envelope{ Payload: base64.StdEncoding.EncodeToString(body), PayloadType: payloadType, } paeEnc := PAE(payloadType, body) for _, signer := range es.providers { sig, err := signer.Sign(ctx, paeEnc) if err != nil { return nil, err } keyID, err := signer.KeyID() if err != nil { keyID = "" } e.Signatures = append(e.Signatures, Signature{ KeyID: keyID, Sig: base64.StdEncoding.EncodeToString(sig), }) } return &e, nil } go-securesystemslib-0.8.0/dsse/sign_test.go000066400000000000000000000240141454036212700210200ustar00rootroot00000000000000package dsse import ( "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/sha256" "encoding/base64" "errors" "fmt" "math/big" "testing" "github.com/codahale/rfc6979" "github.com/stretchr/testify/assert" ) var errLength = errors.New("invalid length") func TestPAE(t *testing.T) { t.Run("Empty", func(t *testing.T) { var want = []byte("DSSEv1 0 0 ") got := PAE("", []byte{}) assert.Equal(t, want, got, "Wrong encoding") }) t.Run("Hello world", func(t *testing.T) { var want = []byte("DSSEv1 29 http://example.com/HelloWorld 11 hello world") got := PAE("http://example.com/HelloWorld", []byte("hello world")) assert.Equal(t, want, got, "Wrong encoding") }) t.Run("Unicode-only", func(t *testing.T) { var want = []byte("DSSEv1 29 http://example.com/HelloWorld 3 ಠ") got := PAE("http://example.com/HelloWorld", []byte("ಠ")) assert.Equal(t, want, got, "Wrong encoding") }) } type nilSignerVerifier int func (n nilSignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) { return data, nil } func (n nilSignerVerifier) Verify(ctx context.Context, data, sig []byte) error { if len(data) != len(sig) { return errLength } for i := range data { if data[i] != sig[i] { return errVerify } } return nil } func (n nilSignerVerifier) KeyID() (string, error) { return "nil", nil } func (n nilSignerVerifier) Public() crypto.PublicKey { return "nil-public" } type nullSignerVerifier int func (n nullSignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) { return data, nil } func (n nullSignerVerifier) Verify(ctx context.Context, data, sig []byte) error { if len(data) != len(sig) { return errLength } for i := range data { if data[i] != sig[i] { return errVerify } } return nil } func (n nullSignerVerifier) KeyID() (string, error) { return "null", nil } func (n nullSignerVerifier) Public() crypto.PublicKey { return "null-public" } type errsigner int func (n errsigner) Sign(ctx context.Context, data []byte) ([]byte, error) { return nil, fmt.Errorf("signing error") } func (n errsigner) Verify(ctx context.Context, data, sig []byte) error { return errVerify } func (n errsigner) KeyID() (string, error) { return "err", nil } func (n errsigner) Public() crypto.PublicKey { return "err-public" } type errSignerVerifier int var errVerify = fmt.Errorf("accepted signatures do not match threshold, Found: 0, Expected 1") var errThreshold = fmt.Errorf("invalid threshold") func (n errSignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) { return data, nil } func (n errSignerVerifier) Verify(ctx context.Context, data, sig []byte) error { return errVerify } func (n errSignerVerifier) KeyID() (string, error) { return "err", nil } func (n errSignerVerifier) Public() crypto.PublicKey { return "err-public" } type badverifier int func (n badverifier) Sign(ctx context.Context, data []byte) ([]byte, error) { return append(data, byte(0)), nil } func (n badverifier) Verify(ctx context.Context, data, sig []byte) error { if len(data) != len(sig) { return errLength } for i := range data { if data[i] != sig[i] { return errVerify } } return nil } func (n badverifier) KeyID() (string, error) { return "bad", nil } func (n badverifier) Public() crypto.PublicKey { return "bad-public" } func TestNoSigners(t *testing.T) { t.Run("nil slice", func(t *testing.T) { signer, err := NewEnvelopeSigner(nil) assert.Nil(t, signer, "unexpected signer") assert.NotNil(t, err, "error expected") assert.Equal(t, ErrNoSigners, err, "wrong error") }) t.Run("empty slice", func(t *testing.T) { signer, err := NewEnvelopeSigner([]Signer{}...) assert.Nil(t, signer, "unexpected signer") assert.NotNil(t, err, "error expected") assert.Equal(t, ErrNoSigners, err, "wrong error") }) } func TestNilSign(t *testing.T) { var keyID = "nil" var payloadType = "http://example.com/HelloWorld" var payload = []byte("hello world") pae := PAE(payloadType, payload) want := Envelope{ Payload: base64.StdEncoding.EncodeToString([]byte(payload)), PayloadType: payloadType, Signatures: []Signature{ { KeyID: keyID, Sig: base64.StdEncoding.EncodeToString(pae), }, }, } var ns nilSignerVerifier signer, err := NewEnvelopeSigner(ns) assert.Nil(t, err, "unexpected error") got, err := signer.SignPayload(context.TODO(), payloadType, []byte(payload)) assert.Nil(t, err, "sign failed") assert.Equal(t, &want, got, "bad signature") } func TestSignError(t *testing.T) { var es errsigner signer, err := NewEnvelopeSigner(es) assert.Nil(t, err, "unexpected error") got, err := signer.SignPayload(context.TODO(), "t", []byte("d")) assert.Nil(t, got, "expected nil") assert.NotNil(t, err, "error expected") assert.Equal(t, "signing error", err.Error(), "wrong error") } func newEcdsaKey() *ecdsa.PrivateKey { var x big.Int var y big.Int var d big.Int _, ok := x.SetString("46950820868899156662930047687818585632848591499744589407958293238635476079160", 10) if !ok { return nil } _, ok = y.SetString("5640078356564379163099075877009565129882514886557779369047442380624545832820", 10) if !ok { return nil } _, ok = d.SetString("97358161215184420915383655311931858321456579547487070936769975997791359926199", 10) if !ok { return nil } var private = ecdsa.PrivateKey{ PublicKey: ecdsa.PublicKey{ Curve: elliptic.P256(), X: &x, Y: &y, }, D: &d, } return &private } type ecdsaSignerVerifier struct { keyID string key *ecdsa.PrivateKey rLen int verified bool } func (es *ecdsaSignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) { // Data is complete message, hash it and sign the digest digest := sha256.Sum256(data) r, s, err := rfc6979.SignECDSA(es.key, digest[:], sha256.New) if err != nil { return nil, err } rb := r.Bytes() sb := s.Bytes() es.rLen = len(rb) rawSig := append(rb, sb...) return rawSig, nil } func (es *ecdsaSignerVerifier) Verify(ctx context.Context, data, sig []byte) error { var r big.Int var s big.Int digest := sha256.Sum256(data) // Signature here is the raw bytes of r and s concatenated rb := sig[:es.rLen] sb := sig[es.rLen:] r.SetBytes(rb) s.SetBytes(sb) ok := ecdsa.Verify(&es.key.PublicKey, digest[:], &r, &s) es.verified = ok if ok { return nil } return errVerify } func (es *ecdsaSignerVerifier) KeyID() (string, error) { return es.keyID, nil } func (es *ecdsaSignerVerifier) Public() crypto.PublicKey { return es.key.Public() } // Test against the example in the protocol specification: // https://github.com/secure-systems-lab/dsse/blob/master/protocol.md func TestEcdsaSign(t *testing.T) { var keyID = "test key 123" var payloadType = "http://example.com/HelloWorld" var payload = "hello world" var ecdsa = &ecdsaSignerVerifier{ keyID: keyID, key: newEcdsaKey(), } var want = Envelope{ Payload: "aGVsbG8gd29ybGQ=", PayloadType: payloadType, Signatures: []Signature{ { KeyID: keyID, Sig: "A3JqsQGtVsJ2O2xqrI5IcnXip5GToJ3F+FnZ+O88SjtR6rDAajabZKciJTfUiHqJPcIAriEGAHTVeCUjW2JIZA==", }, }, } signer, err := NewEnvelopeSigner(ecdsa) assert.Nil(t, err, "unexpected error") env, err := signer.SignPayload(context.TODO(), payloadType, []byte(payload)) assert.Nil(t, err, "unexpected error") assert.Equal(t, &want, env, "Wrong envelope generated") // Now verify verifier, err := NewEnvelopeVerifier(ecdsa) assert.Nil(t, err, "unexpected error") acceptedKeys, err := verifier.Verify(context.TODO(), env) assert.Nil(t, err, "unexpected error") assert.True(t, ecdsa.verified, "verify was not called") assert.Len(t, acceptedKeys, 1, "unexpected keys") assert.Equal(t, acceptedKeys[0].KeyID, keyID, "unexpected keyid") } func TestDecodeB64Payload(t *testing.T) { var want = make([]byte, 256) for i := range want { want[i] = byte(i) } var b64Url = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==" var b64Std = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w==" var b64UrlErr = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w" var b64StdErr = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w" t.Run("Standard encoding", func(t *testing.T) { env := &Envelope{ Payload: b64Std, } got, err := env.DecodeB64Payload() assert.Nil(t, err, "unexpected error") assert.Equal(t, want, got, "wrong data") }) t.Run("URL encoding", func(t *testing.T) { env := &Envelope{ Payload: b64Url, } got, err := env.DecodeB64Payload() assert.Nil(t, err, "unexpected error") assert.Equal(t, want, got, "wrong data") }) t.Run("Standard encoding - error", func(t *testing.T) { env := &Envelope{ Payload: b64StdErr, } got, err := env.DecodeB64Payload() assert.NotNil(t, err, "expected error") assert.Nil(t, got, "wrong data") }) t.Run("URL encoding - error", func(t *testing.T) { env := &Envelope{ Payload: b64UrlErr, } got, err := env.DecodeB64Payload() assert.NotNil(t, err, "expected error") assert.Nil(t, got, "wrong data") }) } go-securesystemslib-0.8.0/dsse/signerverifier.go000066400000000000000000000025761454036212700220550ustar00rootroot00000000000000package dsse import ( "context" "crypto" ) /* Signer defines the interface for an abstract signing algorithm. The Signer interface is used to inject signature algorithm implementations into the EnvelopeSigner. This decoupling allows for any signing algorithm and key management system can be used. The full message is provided as the parameter. If the signature algorithm depends on hashing of the message prior to signature calculation, the implementor of this interface must perform such hashing. The function must return raw bytes representing the calculated signature using the current algorithm, and the key used (if applicable). */ type Signer interface { Sign(ctx context.Context, data []byte) ([]byte, error) KeyID() (string, error) } /* Verifier verifies a complete message against a signature and key. If the message was hashed prior to signature generation, the verifier must perform the same steps. If KeyID returns successfully, only signature matching the key ID will be verified. */ type Verifier interface { Verify(ctx context.Context, data, sig []byte) error KeyID() (string, error) Public() crypto.PublicKey } // SignerVerifier provides both the signing and verification interface. type SignerVerifier interface { Signer Verifier } // Deprecated: switch to renamed SignerVerifier. This is currently aliased for // backwards compatibility. type SignVerifier = SignerVerifier go-securesystemslib-0.8.0/dsse/verify.go000066400000000000000000000064131454036212700203300ustar00rootroot00000000000000package dsse import ( "context" "crypto" "errors" "fmt" "golang.org/x/crypto/ssh" ) // ErrNoSignature indicates that an envelope did not contain any signatures. var ErrNoSignature = errors.New("no signature found") type EnvelopeVerifier struct { providers []Verifier threshold int } type AcceptedKey struct { Public crypto.PublicKey KeyID string Sig Signature } func (ev *EnvelopeVerifier) Verify(ctx context.Context, e *Envelope) ([]AcceptedKey, error) { if e == nil { return nil, errors.New("cannot verify a nil envelope") } if len(e.Signatures) == 0 { return nil, ErrNoSignature } // Decode payload (i.e serialized body) body, err := e.DecodeB64Payload() if err != nil { return nil, err } // Generate PAE(payloadtype, serialized body) paeEnc := PAE(e.PayloadType, body) // If *any* signature is found to be incorrect, it is skipped var acceptedKeys []AcceptedKey usedKeyids := make(map[string]string) unverified_providers := ev.providers for _, s := range e.Signatures { sig, err := b64Decode(s.Sig) if err != nil { return nil, err } // Loop over the providers. // If provider and signature include key IDs but do not match skip. // If a provider recognizes the key, we exit // the loop and use the result. providers := unverified_providers for i, v := range providers { keyID, err := v.KeyID() // Verifiers that do not provide a keyid will be generated one using public. if err != nil || keyID == "" { keyID, err = SHA256KeyID(v.Public()) if err != nil { keyID = "" } } if s.KeyID != "" && keyID != "" && err == nil && s.KeyID != keyID { continue } err = v.Verify(ctx, paeEnc, sig) if err != nil { continue } acceptedKey := AcceptedKey{ Public: v.Public(), KeyID: keyID, Sig: s, } unverified_providers = removeIndex(providers, i) // See https://github.com/in-toto/in-toto/pull/251 if _, ok := usedKeyids[keyID]; ok { fmt.Printf("Found envelope signed by different subkeys of the same main key, Only one of them is counted towards the step threshold, KeyID=%s\n", keyID) continue } usedKeyids[keyID] = "" acceptedKeys = append(acceptedKeys, acceptedKey) break } } // Sanity if with some reflect magic this happens. if ev.threshold <= 0 || ev.threshold > len(ev.providers) { return nil, errors.New("invalid threshold") } if len(usedKeyids) < ev.threshold { return acceptedKeys, fmt.Errorf("accepted signatures do not match threshold, Found: %d, Expected %d", len(acceptedKeys), ev.threshold) } return acceptedKeys, nil } func NewEnvelopeVerifier(v ...Verifier) (*EnvelopeVerifier, error) { return NewMultiEnvelopeVerifier(1, v...) } func NewMultiEnvelopeVerifier(threshold int, p ...Verifier) (*EnvelopeVerifier, error) { if threshold <= 0 || threshold > len(p) { return nil, errors.New("invalid threshold") } ev := EnvelopeVerifier{ providers: p, threshold: threshold, } return &ev, nil } func SHA256KeyID(pub crypto.PublicKey) (string, error) { // Generate public key fingerprint sshpk, err := ssh.NewPublicKey(pub) if err != nil { return "", err } fingerprint := ssh.FingerprintSHA256(sshpk) return fingerprint, nil } func removeIndex(v []Verifier, index int) []Verifier { return append(v[:index], v[index+1:]...) } go-securesystemslib-0.8.0/dsse/verify_test.go000066400000000000000000000243731454036212700213740ustar00rootroot00000000000000package dsse import ( "context" "crypto" "errors" "fmt" "testing" "github.com/stretchr/testify/assert" ) func TestEnvelopeVerifier_Verify_HandlesNil(t *testing.T) { verifier, err := NewEnvelopeVerifier(&mockVerifier{}) assert.NoError(t, err) acceptedKeys, err := verifier.Verify(context.TODO(), nil) assert.Empty(t, acceptedKeys) assert.EqualError(t, err, "cannot verify a nil envelope") } type mockVerifier struct { returnErr error } func (m *mockVerifier) Verify(ctx context.Context, data, sig []byte) error { if m.returnErr != nil { return m.returnErr } return nil } func (m *mockVerifier) KeyID() (string, error) { return "mock", errors.New("Unsupported keyid") } func (m *mockVerifier) Public() crypto.PublicKey { return "mock-public" } // Test against the example in the protocol specification: // https://github.com/secure-systems-lab/dsse/blob/master/protocol.md func TestVerify(t *testing.T) { var keyID = "test key 123" var payloadType = "http://example.com/HelloWorld" e := Envelope{ Payload: "aGVsbG8gd29ybGQ=", PayloadType: payloadType, Signatures: []Signature{ { KeyID: keyID, Sig: "Cc3RkvYsLhlaFVd+d6FPx4ZClhqW4ZT0rnCYAfv6/ckoGdwT7g/blWNpOBuL/tZhRiVFaglOGTU8GEjm4aEaNA==", }, }, } ev, err := NewEnvelopeVerifier(&mockVerifier{}) assert.Nil(t, err, "unexpected error") acceptedKeys, err := ev.Verify(context.TODO(), &e) // Now verify assert.Nil(t, err, "unexpected error") assert.Len(t, acceptedKeys, 1, "unexpected keys") assert.Equal(t, acceptedKeys[0].KeyID, "", "unexpected keyid") // Now try an error ev, err = NewEnvelopeVerifier(&mockVerifier{returnErr: errors.New("uh oh")}) assert.Nil(t, err, "unexpected error") _, err = ev.Verify(context.TODO(), &e) // Now verify assert.Error(t, err) } func TestVerifyOneProvider(t *testing.T) { var payloadType = "http://example.com/HelloWorld" var payload = "hello world" var ns nilSignerVerifier signer, err := NewEnvelopeSigner(ns) assert.Nil(t, err, "unexpected error") env, err := signer.SignPayload(context.TODO(), payloadType, []byte(payload)) assert.Nil(t, err, "sign failed") verifier, err := NewEnvelopeVerifier(ns) assert.Nil(t, err, "unexpected error") acceptedKeys, err := verifier.Verify(context.TODO(), env) assert.Nil(t, err, "unexpected error") assert.Len(t, acceptedKeys, 1, "unexpected keys") assert.Equal(t, acceptedKeys[0].KeyID, "nil", "unexpected keyid") } func TestVerifyMultipleProvider(t *testing.T) { var payloadType = "http://example.com/HelloWorld" var payload = "hello world" var ns nilSignerVerifier var null nullSignerVerifier signer, err := NewEnvelopeSigner(ns, null) assert.Nil(t, err, "unexpected error") env, err := signer.SignPayload(context.TODO(), payloadType, []byte(payload)) assert.Nil(t, err, "sign failed") verifier, err := NewEnvelopeVerifier(ns, null) assert.Nil(t, err, "unexpected error") acceptedKeys, err := verifier.Verify(context.TODO(), env) assert.Nil(t, err, "unexpected error") assert.Len(t, acceptedKeys, 2, "unexpected keys") } func TestVerifyMultipleProviderThreshold(t *testing.T) { var payloadType = "http://example.com/HelloWorld" var payload = "hello world" var ns nilSignerVerifier var null nullSignerVerifier signer, err := NewEnvelopeSigner(ns, null) assert.Nil(t, err) env, err := signer.SignPayload(context.TODO(), payloadType, []byte(payload)) assert.Nil(t, err, "sign failed") verifier, err := NewMultiEnvelopeVerifier(2, ns, null) assert.Nil(t, err, "unexpected error") acceptedKeys, err := verifier.Verify(context.TODO(), env) assert.Nil(t, err, "unexpected error") assert.Len(t, acceptedKeys, 2, "unexpected keys") } func TestVerifyMultipleProviderThresholdErr(t *testing.T) { var ns nilSignerVerifier var null nullSignerVerifier _, err := NewMultiEnvelopeVerifier(3, ns, null) assert.Equal(t, errThreshold, err, "wrong error") _, err = NewMultiEnvelopeVerifier(0, ns, null) assert.Equal(t, errThreshold, err, "wrong error") } func TestVerifyErr(t *testing.T) { var payloadType = "http://example.com/HelloWorld" var payload = "hello world" var errsv errSignerVerifier signer, err := NewEnvelopeSigner(errsv) assert.Nil(t, err, "unexpected error") env, err := signer.SignPayload(context.TODO(), payloadType, []byte(payload)) assert.Nil(t, err, "sign failed") verifier, err := NewEnvelopeVerifier(errsv) assert.Nil(t, err, "unexpected error") _, err = verifier.Verify(context.TODO(), env) assert.Equal(t, errVerify, err, "wrong error") } func TestBadVerifier(t *testing.T) { var payloadType = "http://example.com/HelloWorld" var payload = "hello world" var badv badverifier signer, err := NewEnvelopeSigner(badv) assert.Nil(t, err, "unexpected error") env, err := signer.SignPayload(context.TODO(), payloadType, []byte(payload)) assert.Nil(t, err, "sign failed") verifier, err := NewEnvelopeVerifier(badv) assert.Nil(t, err, "unexpected error") _, err = verifier.Verify(context.TODO(), env) assert.NotNil(t, err, "expected error") } func TestVerifyNoSig(t *testing.T) { var badv badverifier verifier, err := NewEnvelopeVerifier(badv) assert.Nil(t, err, "unexpected error") env := &Envelope{} _, err = verifier.Verify(context.TODO(), env) assert.Equal(t, ErrNoSignature, err, "wrong error") } func TestVerifyBadBase64(t *testing.T) { var badv badverifier verifier, err := NewEnvelopeVerifier(badv) assert.Nil(t, err, "unexpected error") expectedErr := fmt.Errorf("unable to base64 decode payload (is payload in the right format?)") t.Run("Payload", func(t *testing.T) { env := &Envelope{ Payload: "Not base 64", Signatures: []Signature{ {}, }, } _, err := verifier.Verify(context.TODO(), env) assert.IsType(t, expectedErr, err, "wrong error") }) t.Run("Signature", func(t *testing.T) { env := &Envelope{ Payload: "cGF5bG9hZAo=", Signatures: []Signature{ { Sig: "not base 64", }, }, } _, err := verifier.Verify(context.TODO(), env) assert.IsType(t, expectedErr, err, "wrong error") }) } func TestVerifyNoMatch(t *testing.T) { var payloadType = "http://example.com/HelloWorld" var ns nilSignerVerifier var null nullSignerVerifier verifier, err := NewEnvelopeVerifier(ns, null) assert.Nil(t, err, "unexpected error") env := &Envelope{ PayloadType: payloadType, Payload: "cGF5bG9hZAo=", Signatures: []Signature{ { KeyID: "not found", Sig: "cGF5bG9hZAo=", }, }, } _, err = verifier.Verify(context.TODO(), env) assert.NotNil(t, err, "expected error") } type interceptSignerVerifier struct { keyID string verifyRes bool verifyCalled bool } func (i *interceptSignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) { return data, nil } func (i *interceptSignerVerifier) Verify(ctx context.Context, data, sig []byte) error { i.verifyCalled = true if i.verifyRes { return nil } return errVerify } func (i *interceptSignerVerifier) KeyID() (string, error) { return i.keyID, nil } func (i *interceptSignerVerifier) Public() crypto.PublicKey { return "intercept-public" } func TestVerifyOneFail(t *testing.T) { var payloadType = "http://example.com/HelloWorld" var payload = "hello world" var s1 = &interceptSignerVerifier{ keyID: "i1", verifyRes: true, } var s2 = &interceptSignerVerifier{ keyID: "i2", verifyRes: false, } signer, err := NewEnvelopeSigner(s1, s2) assert.Nil(t, err, "unexpected error") env, err := signer.SignPayload(context.TODO(), payloadType, []byte(payload)) assert.Nil(t, err, "sign failed") verifier, err := NewEnvelopeVerifier(s1, s2) assert.Nil(t, err, "unexpected error") acceptedKeys, err := verifier.Verify(context.TODO(), env) assert.Nil(t, err, "expected error") assert.True(t, s1.verifyCalled, "verify not called") assert.True(t, s2.verifyCalled, "verify not called") assert.Len(t, acceptedKeys, 1, "unexpected keys") assert.Equal(t, acceptedKeys[0].KeyID, "i1", "unexpected keyid") } func TestVerifySameKeyID(t *testing.T) { var payloadType = "http://example.com/HelloWorld" var payload = "hello world" var s1 = &interceptSignerVerifier{ keyID: "i1", verifyRes: true, } var s2 = &interceptSignerVerifier{ keyID: "i1", verifyRes: true, } signer, err := NewEnvelopeSigner(s1, s2) assert.Nil(t, err, "unexpected error") env, err := signer.SignPayload(context.TODO(), payloadType, []byte(payload)) assert.Nil(t, err, "sign failed") verifier, err := NewEnvelopeVerifier(s1, s2) assert.Nil(t, err, "unexpected error") acceptedKeys, err := verifier.Verify(context.TODO(), env) assert.Nil(t, err, "expected error") assert.True(t, s1.verifyCalled, "verify not called") assert.True(t, s2.verifyCalled, "verify not called") assert.Len(t, acceptedKeys, 1, "unexpected keys") assert.Equal(t, acceptedKeys[0].KeyID, "i1", "unexpected keyid") } func TestVerifyEmptyKeyID(t *testing.T) { var payloadType = "http://example.com/HelloWorld" var payload = "hello world" var s1 = &interceptSignerVerifier{ keyID: "", verifyRes: true, } var s2 = &interceptSignerVerifier{ keyID: "", verifyRes: true, } signer, err := NewEnvelopeSigner(s1, s2) assert.Nil(t, err, "unexpected error") env, err := signer.SignPayload(context.TODO(), payloadType, []byte(payload)) assert.Nil(t, err, "sign failed") verifier, err := NewEnvelopeVerifier(s1, s2) assert.Nil(t, err, "unexpected error") acceptedKeys, err := verifier.Verify(context.TODO(), env) assert.Nil(t, err, "expected error") assert.Len(t, acceptedKeys, 1, "unexpected keys") assert.Equal(t, acceptedKeys[0].KeyID, "", "unexpected keyid") } func TestVerifyPublicKeyID(t *testing.T) { var payloadType = "http://example.com/HelloWorld" var payload = "hello world" var keyID = "SHA256:f4AuBLdH4Lj/dIuwAUXXebzoI9B/cJ4iSQ3/qByIl4M" var s1 = &ecdsaSignerVerifier{ keyID: "", key: newEcdsaKey(), } var s2 = &ecdsaSignerVerifier{ keyID: "", key: newEcdsaKey(), } signer, err := NewEnvelopeSigner(s1, s2) assert.Nil(t, err, "unexpected error") env, err := signer.SignPayload(context.TODO(), payloadType, []byte(payload)) assert.Nil(t, err, "sign failed") verifier, err := NewEnvelopeVerifier(s1, s2) assert.Nil(t, err, "unexpected error") acceptedKeys, err := verifier.Verify(context.TODO(), env) assert.Nil(t, err, "expected error") assert.Len(t, acceptedKeys, 1, "unexpected keys") assert.Equal(t, acceptedKeys[0].KeyID, keyID, "unexpected keyid") } go-securesystemslib-0.8.0/encrypted/000077500000000000000000000000001454036212700175305ustar00rootroot00000000000000go-securesystemslib-0.8.0/encrypted/encrypted.go000066400000000000000000000162671454036212700220700ustar00rootroot00000000000000// Package encrypted provides a simple, secure system for encrypting data // symmetrically with a passphrase. // // It uses scrypt derive a key from the passphrase and the NaCl secret box // cipher for authenticated encryption. package encrypted import ( "crypto/rand" "encoding/json" "errors" "fmt" "io" "golang.org/x/crypto/nacl/secretbox" "golang.org/x/crypto/scrypt" ) const saltSize = 32 const ( boxKeySize = 32 boxNonceSize = 24 ) // KDFParameterStrength defines the KDF parameter strength level to be used for // encryption key derivation. type KDFParameterStrength uint8 const ( // Legacy defines legacy scrypt parameters (N:2^15, r:8, p:1) Legacy KDFParameterStrength = iota + 1 // Standard defines standard scrypt parameters which is focusing 100ms of computation (N:2^16, r:8, p:1) Standard // OWASP defines OWASP recommended scrypt parameters (N:2^17, r:8, p:1) OWASP ) var ( // legacyParams represents old scrypt derivation parameters for backward // compatibility. legacyParams = scryptParams{ N: 32768, // 2^15 R: 8, P: 1, } // standardParams defines scrypt parameters based on the scrypt creator // recommendation to limit key derivation in time boxed to 100ms. standardParams = scryptParams{ N: 65536, // 2^16 R: 8, P: 1, } // owaspParams defines scrypt parameters recommended by OWASP owaspParams = scryptParams{ N: 131072, // 2^17 R: 8, P: 1, } // defaultParams defines scrypt parameters which will be used to generate a // new key. defaultParams = standardParams ) const ( nameScrypt = "scrypt" nameSecretBox = "nacl/secretbox" ) type data struct { KDF scryptKDF `json:"kdf"` Cipher secretBoxCipher `json:"cipher"` Ciphertext []byte `json:"ciphertext"` } type scryptParams struct { N int `json:"N"` R int `json:"r"` P int `json:"p"` } func (sp *scryptParams) Equal(in *scryptParams) bool { return in != nil && sp.N == in.N && sp.P == in.P && sp.R == in.R } func newScryptKDF(level KDFParameterStrength) (scryptKDF, error) { salt := make([]byte, saltSize) if err := fillRandom(salt); err != nil { return scryptKDF{}, fmt.Errorf("unable to generate a random salt: %w", err) } var params scryptParams switch level { case Legacy: params = legacyParams case Standard: params = standardParams case OWASP: params = owaspParams default: // Fallback to default parameters params = defaultParams } return scryptKDF{ Name: nameScrypt, Params: params, Salt: salt, }, nil } type scryptKDF struct { Name string `json:"name"` Params scryptParams `json:"params"` Salt []byte `json:"salt"` } func (s *scryptKDF) Key(passphrase []byte) ([]byte, error) { return scrypt.Key(passphrase, s.Salt, s.Params.N, s.Params.R, s.Params.P, boxKeySize) } // CheckParams checks that the encoded KDF parameters are what we expect them to // be. If we do not do this, an attacker could cause a DoS by tampering with // them. func (s *scryptKDF) CheckParams() error { switch { case legacyParams.Equal(&s.Params): case standardParams.Equal(&s.Params): case owaspParams.Equal(&s.Params): default: return errors.New("unsupported scrypt parameters") } return nil } func newSecretBoxCipher() (secretBoxCipher, error) { nonce := make([]byte, boxNonceSize) if err := fillRandom(nonce); err != nil { return secretBoxCipher{}, err } return secretBoxCipher{ Name: nameSecretBox, Nonce: nonce, }, nil } type secretBoxCipher struct { Name string `json:"name"` Nonce []byte `json:"nonce"` encrypted bool } func (s *secretBoxCipher) Encrypt(plaintext, key []byte) []byte { var keyBytes [boxKeySize]byte var nonceBytes [boxNonceSize]byte if len(key) != len(keyBytes) { panic("incorrect key size") } if len(s.Nonce) != len(nonceBytes) { panic("incorrect nonce size") } copy(keyBytes[:], key) copy(nonceBytes[:], s.Nonce) // ensure that we don't re-use nonces if s.encrypted { panic("Encrypt must only be called once for each cipher instance") } s.encrypted = true return secretbox.Seal(nil, plaintext, &nonceBytes, &keyBytes) } func (s *secretBoxCipher) Decrypt(ciphertext, key []byte) ([]byte, error) { var keyBytes [boxKeySize]byte var nonceBytes [boxNonceSize]byte if len(key) != len(keyBytes) { panic("incorrect key size") } if len(s.Nonce) != len(nonceBytes) { // return an error instead of panicking since the nonce is user input return nil, errors.New("encrypted: incorrect nonce size") } copy(keyBytes[:], key) copy(nonceBytes[:], s.Nonce) res, ok := secretbox.Open(nil, ciphertext, &nonceBytes, &keyBytes) if !ok { return nil, errors.New("encrypted: decryption failed") } return res, nil } // Encrypt takes a passphrase and plaintext, and returns a JSON object // containing ciphertext and the details necessary to decrypt it. func Encrypt(plaintext, passphrase []byte) ([]byte, error) { return EncryptWithCustomKDFParameters(plaintext, passphrase, Standard) } // EncryptWithCustomKDFParameters takes a passphrase, the plaintext and a KDF // parameter level (Legacy, Standard, or OWASP), and returns a JSON object // containing ciphertext and the details necessary to decrypt it. func EncryptWithCustomKDFParameters(plaintext, passphrase []byte, kdfLevel KDFParameterStrength) ([]byte, error) { k, err := newScryptKDF(kdfLevel) if err != nil { return nil, err } key, err := k.Key(passphrase) if err != nil { return nil, err } c, err := newSecretBoxCipher() if err != nil { return nil, err } data := &data{ KDF: k, Cipher: c, } data.Ciphertext = c.Encrypt(plaintext, key) return json.Marshal(data) } // Marshal encrypts the JSON encoding of v using passphrase. func Marshal(v interface{}, passphrase []byte) ([]byte, error) { return MarshalWithCustomKDFParameters(v, passphrase, Standard) } // MarshalWithCustomKDFParameters encrypts the JSON encoding of v using passphrase. func MarshalWithCustomKDFParameters(v interface{}, passphrase []byte, kdfLevel KDFParameterStrength) ([]byte, error) { data, err := json.MarshalIndent(v, "", "\t") if err != nil { return nil, err } return EncryptWithCustomKDFParameters(data, passphrase, kdfLevel) } // Decrypt takes a JSON-encoded ciphertext object encrypted using Encrypt and // tries to decrypt it using passphrase. If successful, it returns the // plaintext. func Decrypt(ciphertext, passphrase []byte) ([]byte, error) { data := &data{} if err := json.Unmarshal(ciphertext, data); err != nil { return nil, err } if data.KDF.Name != nameScrypt { return nil, fmt.Errorf("encrypted: unknown kdf name %q", data.KDF.Name) } if data.Cipher.Name != nameSecretBox { return nil, fmt.Errorf("encrypted: unknown cipher name %q", data.Cipher.Name) } if err := data.KDF.CheckParams(); err != nil { return nil, err } key, err := data.KDF.Key(passphrase) if err != nil { return nil, err } return data.Cipher.Decrypt(data.Ciphertext, key) } // Unmarshal decrypts the data using passphrase and unmarshals the resulting // plaintext into the value pointed to by v. func Unmarshal(data []byte, v interface{}, passphrase []byte) error { decrypted, err := Decrypt(data, passphrase) if err != nil { return err } return json.Unmarshal(decrypted, v) } func fillRandom(b []byte) error { _, err := io.ReadFull(rand.Reader, b) return err } go-securesystemslib-0.8.0/encrypted/encrypted_test.go000066400000000000000000000110721454036212700231140ustar00rootroot00000000000000package encrypted import ( "encoding/json" "testing" "github.com/stretchr/testify/assert" ) var ( kdfVectors = map[KDFParameterStrength][]byte{ Legacy: []byte(`{"kdf":{"name":"scrypt","params":{"N":32768,"r":8,"p":1},"salt":"WO3mVvyTwJ9vwT5/Tk5OW5WPIBUofMjcpEfrLnfY4uA="},"cipher":{"name":"nacl/secretbox","nonce":"tCy7HcTFr4uxv4Nrg/DWmncuZ148U1MX"},"ciphertext":"08n43p5G5yviPEZpO7tPPF4aZQkWiWjkv4taFdhDBA0tamKH4nw="}`), Standard: []byte(`{"kdf":{"name":"scrypt","params":{"N":65536,"r":8,"p":1},"salt":"FhzPOt9/bJG4PTq6lQ6ecG6GzaOuOy/ynG5+yRiFlNs="},"cipher":{"name":"nacl/secretbox","nonce":"aw1ng1jHaDz/tQ7V2gR9O2+IGQ8xJEuE"},"ciphertext":"HycvuLZL4sYH0BrYTh4E/H20VtAW6u5zL5Pr+IBjYLYnCPzDkq8="}`), OWASP: []byte(`{"kdf":{"name":"scrypt","params":{"N":131072,"r":8,"p":1},"salt":"m38E3kouJTtiheLQN22NQ8DTito5hrjpUIskqcd375k="},"cipher":{"name":"nacl/secretbox","nonce":"Y6PM13yA+o44pE/W1ZBwczeGnTV/m9Zc"},"ciphertext":"6H8sqj1K6B6yDjtH5AQ6lbFigg/C2yDDJc4rYJ79w9aVPImFIPI="}`), } ) var plaintext = []byte("reallyimportant") func TestRoundtrip(t *testing.T) { passphrase := []byte("supersecret") enc, err := Encrypt(plaintext, passphrase) assert.Nil(t, err) // successful decrypt dec, err := Decrypt(enc, passphrase) assert.Nil(t, err) assert.Equal(t, plaintext, dec) // wrong passphrase passphrase[0] = 0 dec, err = Decrypt(enc, passphrase) assert.NotNil(t, err) assert.Nil(t, dec) } func TestTamperedRoundtrip(t *testing.T) { passphrase := []byte("supersecret") enc, err := Encrypt(plaintext, passphrase) assert.Nil(t, err) data := &data{} err = json.Unmarshal(enc, data) assert.Nil(t, err) data.Ciphertext[0] = ^data.Ciphertext[0] enc, _ = json.Marshal(data) dec, err := Decrypt(enc, passphrase) assert.NotNil(t, err) assert.Nil(t, dec) } func TestDecrypt(t *testing.T) { enc := []byte(`{"kdf":{"name":"scrypt","params":{"N":32768,"r":8,"p":1},"salt":"N9a7x5JFGbrtB2uBR81jPwp0eiLR4A7FV3mjVAQrg1g="},"cipher":{"name":"nacl/secretbox","nonce":"2h8HxMmgRfuYdpswZBQaU3xJ1nkA/5Ik"},"ciphertext":"SEW6sUh0jf2wfdjJGPNS9+bkk2uB+Cxamf32zR8XkQ=="}`) passphrase := []byte("supersecret") dec, err := Decrypt(enc, passphrase) assert.Nil(t, err) assert.Equal(t, plaintext, dec) } func TestMarshalUnmarshal(t *testing.T) { passphrase := []byte("supersecret") wrapped, err := Marshal(plaintext, passphrase) assert.Nil(t, err) assert.NotNil(t, wrapped) var protected []byte err = Unmarshal(wrapped, &protected, passphrase) assert.Nil(t, err) assert.Equal(t, plaintext, protected) } func TestInvalidKDFSettings(t *testing.T) { passphrase := []byte("supersecret") wrapped, err := MarshalWithCustomKDFParameters(plaintext, passphrase, 0) assert.Nil(t, err) assert.NotNil(t, wrapped) var protected []byte err = Unmarshal(wrapped, &protected, passphrase) assert.Nil(t, err) assert.Equal(t, plaintext, protected) } func TestLegacyKDFSettings(t *testing.T) { passphrase := []byte("supersecret") wrapped, err := MarshalWithCustomKDFParameters(plaintext, passphrase, Legacy) assert.Nil(t, err) assert.NotNil(t, wrapped) var protected []byte err = Unmarshal(wrapped, &protected, passphrase) assert.Nil(t, err) assert.Equal(t, plaintext, protected) } func TestStandardKDFSettings(t *testing.T) { passphrase := []byte("supersecret") wrapped, err := MarshalWithCustomKDFParameters(plaintext, passphrase, Standard) assert.Nil(t, err) assert.NotNil(t, wrapped) var protected []byte err = Unmarshal(wrapped, &protected, passphrase) assert.Nil(t, err) assert.Equal(t, plaintext, protected) } func TestOWASPKDFSettings(t *testing.T) { passphrase := []byte("supersecret") wrapped, err := MarshalWithCustomKDFParameters(plaintext, passphrase, OWASP) assert.Nil(t, err) assert.NotNil(t, wrapped) var protected []byte err = Unmarshal(wrapped, &protected, passphrase) assert.Nil(t, err) assert.Equal(t, plaintext, protected) } func TestKDFSettingVectors(t *testing.T) { passphrase := []byte("supersecret") for _, v := range kdfVectors { var protected []byte err := Unmarshal(v, &protected, passphrase) assert.Nil(t, err) assert.Equal(t, plaintext, protected) } } func TestUnsupportedKDFParameters(t *testing.T) { enc := []byte(`{"kdf":{"name":"scrypt","params":{"N":99,"r":99,"p":99},"salt":"cZFcQJdwPhPyhU1R4qkl0qVOIjZd4V/7LYYAavq166k="},"cipher":{"name":"nacl/secretbox","nonce":"7vhRS7j0hEPBWV05skAdgLj81AkGeE7U"},"ciphertext":"6WYU/YSXVbYzl/NzaeAzmjLyfFhOOjLc0d8/GFV0aBFdJvyCcXc="}`) passphrase := []byte("supersecret") dec, err := Decrypt(enc, passphrase) assert.NotNil(t, err) assert.Nil(t, dec) assert.ErrorContains(t, err, "unsupported scrypt parameters") } go-securesystemslib-0.8.0/go.mod000066400000000000000000000006111454036212700166370ustar00rootroot00000000000000module github.com/secure-systems-lab/go-securesystemslib go 1.20 require ( github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb github.com/stretchr/testify v1.8.4 golang.org/x/crypto v0.17.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.15.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go-securesystemslib-0.8.0/go.sum000066400000000000000000000027301454036212700166700ustar00rootroot00000000000000github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= go-securesystemslib-0.8.0/signerverifier/000077500000000000000000000000001454036212700205565ustar00rootroot00000000000000go-securesystemslib-0.8.0/signerverifier/ecdsa.go000066400000000000000000000056401454036212700221710ustar00rootroot00000000000000package signerverifier import ( "context" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/sha256" "crypto/sha512" "fmt" "os" ) const ECDSAKeyType = "ecdsa" // ECDSASignerVerifier is a dsse.SignerVerifier compliant interface to sign and // verify signatures using ECDSA keys. type ECDSASignerVerifier struct { keyID string curveSize int private *ecdsa.PrivateKey public *ecdsa.PublicKey } // NewECDSASignerVerifierFromSSLibKey creates an ECDSASignerVerifier from an // SSLibKey. func NewECDSASignerVerifierFromSSLibKey(key *SSLibKey) (*ECDSASignerVerifier, error) { if len(key.KeyVal.Public) == 0 { return nil, ErrInvalidKey } _, publicParsedKey, err := decodeAndParsePEM([]byte(key.KeyVal.Public)) if err != nil { return nil, fmt.Errorf("unable to create ECDSA signerverifier: %w", err) } sv := &ECDSASignerVerifier{ keyID: key.KeyID, curveSize: publicParsedKey.(*ecdsa.PublicKey).Params().BitSize, public: publicParsedKey.(*ecdsa.PublicKey), private: nil, } if len(key.KeyVal.Private) > 0 { _, privateParsedKey, err := decodeAndParsePEM([]byte(key.KeyVal.Private)) if err != nil { return nil, fmt.Errorf("unable to create ECDSA signerverifier: %w", err) } sv.private = privateParsedKey.(*ecdsa.PrivateKey) } return sv, nil } // Sign creates a signature for `data`. func (sv *ECDSASignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) { if sv.private == nil { return nil, ErrNotPrivateKey } hashedData := getECDSAHashedData(data, sv.curveSize) return ecdsa.SignASN1(rand.Reader, sv.private, hashedData) } // Verify verifies the `sig` value passed in against `data`. func (sv *ECDSASignerVerifier) Verify(ctx context.Context, data []byte, sig []byte) error { hashedData := getECDSAHashedData(data, sv.curveSize) if ok := ecdsa.VerifyASN1(sv.public, hashedData, sig); !ok { return ErrSignatureVerificationFailed } return nil } // KeyID returns the identifier of the key used to create the // ECDSASignerVerifier instance. func (sv *ECDSASignerVerifier) KeyID() (string, error) { return sv.keyID, nil } // Public returns the public portion of the key used to create the // ECDSASignerVerifier instance. func (sv *ECDSASignerVerifier) Public() crypto.PublicKey { return sv.public } // LoadECDSAKeyFromFile returns an SSLibKey instance for an ECDSA key stored in // a file in the custom securesystemslib format. func LoadECDSAKeyFromFile(path string) (*SSLibKey, error) { contents, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("unable to load ECDSA key from file: %w", err) } return LoadKeyFromSSLibBytes(contents) } func getECDSAHashedData(data []byte, curveSize int) []byte { switch { case curveSize <= 256: return hashBeforeSigning(data, sha256.New()) case 256 < curveSize && curveSize <= 384: return hashBeforeSigning(data, sha512.New384()) case curveSize > 384: return hashBeforeSigning(data, sha512.New()) } return []byte{} } go-securesystemslib-0.8.0/signerverifier/ecdsa_test.go000066400000000000000000000134001454036212700232210ustar00rootroot00000000000000package signerverifier import ( "context" "encoding/json" "os" "path/filepath" "testing" "github.com/secure-systems-lab/go-securesystemslib/cjson" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/stretchr/testify/assert" ) func TestNewECDSASignerVerifierFromSSLibKey(t *testing.T) { key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub")) if err != nil { t.Fatal(err) } sv, err := NewECDSASignerVerifierFromSSLibKey(key) if err != nil { t.Fatal(err) } expectedPublicString := "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1\nXM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END PUBLIC KEY-----" _, expectedPublicKey, err := decodeAndParsePEM([]byte(expectedPublicString)) assert.Nil(t, err) assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", sv.keyID) assert.Equal(t, expectedPublicKey, sv.public) assert.Nil(t, sv.private) } func TestLoadECDSAKeyFromFile(t *testing.T) { t.Run("ecdsa public key", func(t *testing.T) { key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub")) assert.Nil(t, err) assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", key.KeyID) assert.Equal(t, "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1\nXM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END PUBLIC KEY-----", key.KeyVal.Public) assert.Equal(t, "ecdsa-sha2-nistp256", key.Scheme) assert.Equal(t, ECDSAKeyType, key.KeyType) }) t.Run("ecdsa private key", func(t *testing.T) { key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key")) assert.Nil(t, err) assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", key.KeyID) assert.Equal(t, "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1\nXM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END PUBLIC KEY-----", key.KeyVal.Public) assert.Equal(t, "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIAo6DxXlgqYy+TkvocIOyWlqA3KVtp6dlSY7lS3kkeEMoAoGCCqGSM49\nAwEHoUQDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1XM36oXymJ9wxpM68nCqkrZCV\nnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END EC PRIVATE KEY-----", key.KeyVal.Private) assert.Equal(t, "ecdsa-sha2-nistp256", key.Scheme) assert.Equal(t, ECDSAKeyType, key.KeyType) }) t.Run("invalid path", func(t *testing.T) { _, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "invalid")) assert.ErrorContains(t, err, "unable to load ECDSA key from file") }) } func TestECDSASignerVerifierSign(t *testing.T) { t.Run("using valid key", func(t *testing.T) { key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key")) if err != nil { t.Fatal(err) } sv, err := NewECDSASignerVerifierFromSSLibKey(key) if err != nil { t.Fatal(err) } message := []byte("test message") signature, err := sv.Sign(context.Background(), message) assert.Nil(t, err) err = sv.Verify(context.Background(), message, signature) assert.Nil(t, err) }) t.Run("using invalid key", func(t *testing.T) { key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub")) if err != nil { t.Fatal(err) } sv, err := NewECDSASignerVerifierFromSSLibKey(key) if err != nil { t.Fatal(err) } message := []byte("test message") _, err = sv.Sign(context.Background(), message) assert.ErrorIs(t, err, ErrNotPrivateKey) }) } func TestECDSASignerVerifierWithDSSEEnvelope(t *testing.T) { key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key")) if err != nil { t.Fatal(err) } sv, err := NewECDSASignerVerifierFromSSLibKey(key) if err != nil { t.Fatal(err) } payloadType := "application/vnd.dsse+json" payload := []byte("test message") es, err := dsse.NewEnvelopeSigner(sv) if err != nil { t.Error(err) } env, err := es.SignPayload(context.Background(), payloadType, payload) if err != nil { t.Error(err) } assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", env.Signatures[0].KeyID) envPayload, err := env.DecodeB64Payload() assert.Equal(t, payload, envPayload) assert.Nil(t, err) key, err = LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub")) if err != nil { t.Fatal(err) } sv, err = NewECDSASignerVerifierFromSSLibKey(key) if err != nil { t.Fatal(err) } ev, err := dsse.NewEnvelopeVerifier(sv) if err != nil { t.Error(err) } acceptedKeys, err := ev.Verify(context.Background(), env) assert.Nil(t, err) assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", acceptedKeys[0].KeyID) } func TestECDSASignerVerifierWithMetablockFile(t *testing.T) { key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub")) if err != nil { t.Fatal(err) } sv, err := NewECDSASignerVerifierFromSSLibKey(key) if err != nil { t.Fatal(err) } metadataBytes, err := os.ReadFile(filepath.Join("test-data", "test-ecdsa.98adf386.link")) if err != nil { t.Fatal(err) } mb := struct { Signatures []struct { KeyID string `json:"keyid"` Sig string `json:"sig"` } `json:"signatures"` Signed any `json:"signed"` }{} if err := json.Unmarshal(metadataBytes, &mb); err != nil { t.Fatal(err) } assert.Equal(t, "304502201fbb03c0937504182a48c66f9218bdcb2e99a07ada273e92e5e543867f98c8d7022100dbfa7bbf74fd76d76c1d08676419cba85bbd81dfb000f3ac6a786693ddc508f5", mb.Signatures[0].Sig) assert.Equal(t, sv.keyID, mb.Signatures[0].KeyID) encodedBytes, err := cjson.EncodeCanonical(mb.Signed) if err != nil { t.Fatal(err) } decodedSig := hexDecode(t, mb.Signatures[0].Sig) err = sv.Verify(context.Background(), encodedBytes, decodedSig) assert.Nil(t, err) } go-securesystemslib-0.8.0/signerverifier/ed25519.go000066400000000000000000000055161454036212700221120ustar00rootroot00000000000000package signerverifier import ( "context" "crypto" "crypto/ed25519" "encoding/hex" "fmt" "os" ) const ED25519KeyType = "ed25519" // ED25519SignerVerifier is a dsse.SignerVerifier compliant interface to sign // and verify signatures using ED25519 keys. type ED25519SignerVerifier struct { keyID string private ed25519.PrivateKey public ed25519.PublicKey } // NewED25519SignerVerifierFromSSLibKey creates an Ed25519SignerVerifier from an // SSLibKey. func NewED25519SignerVerifierFromSSLibKey(key *SSLibKey) (*ED25519SignerVerifier, error) { if len(key.KeyVal.Public) == 0 { return nil, ErrInvalidKey } public, err := hex.DecodeString(key.KeyVal.Public) if err != nil { return nil, fmt.Errorf("unable to create ED25519 signerverifier: %w", err) } var private []byte if len(key.KeyVal.Private) > 0 { private, err = hex.DecodeString(key.KeyVal.Private) if err != nil { return nil, fmt.Errorf("unable to create ED25519 signerverifier: %w", err) } // python-securesystemslib provides an interface to generate ed25519 // keys but it differs slightly in how it serializes the key to disk. // Specifically, the keyval.private field includes _only_ the private // portion of the key while libraries such as crypto/ed25519 also expect // the public portion. So, if the private portion is half of what we // expect, we append the public portion as well. if len(private) == ed25519.PrivateKeySize/2 { private = append(private, public...) } } return &ED25519SignerVerifier{ keyID: key.KeyID, public: ed25519.PublicKey(public), private: ed25519.PrivateKey(private), }, nil } // Sign creates a signature for `data`. func (sv *ED25519SignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) { if len(sv.private) == 0 { return nil, ErrNotPrivateKey } signature := ed25519.Sign(sv.private, data) return signature, nil } // Verify verifies the `sig` value passed in against `data`. func (sv *ED25519SignerVerifier) Verify(ctx context.Context, data []byte, sig []byte) error { if ok := ed25519.Verify(sv.public, data, sig); ok { return nil } return ErrSignatureVerificationFailed } // KeyID returns the identifier of the key used to create the // ED25519SignerVerifier instance. func (sv *ED25519SignerVerifier) KeyID() (string, error) { return sv.keyID, nil } // Public returns the public portion of the key used to create the // ED25519SignerVerifier instance. func (sv *ED25519SignerVerifier) Public() crypto.PublicKey { return sv.public } // LoadED25519KeyFromFile returns an SSLibKey instance for an ED25519 key stored // in a file in the custom securesystemslib format. func LoadED25519KeyFromFile(path string) (*SSLibKey, error) { contents, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("unable to load ED25519 key from file: %w", err) } return LoadKeyFromSSLibBytes(contents) } go-securesystemslib-0.8.0/signerverifier/ed25519_test.go000066400000000000000000000146271454036212700231540ustar00rootroot00000000000000package signerverifier import ( "context" "crypto/ed25519" "encoding/json" "os" "path/filepath" "testing" "github.com/secure-systems-lab/go-securesystemslib/cjson" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/stretchr/testify/assert" ) func TestNewED25519SignerVerifierFromSSLibKey(t *testing.T) { key, err := LoadED25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub")) if err != nil { t.Error(err) } sv, err := NewED25519SignerVerifierFromSSLibKey(key) if err != nil { t.Error(err) } expectedPublicString := "3f586ce67329419fb0081bd995914e866a7205da463d593b3b490eab2b27fd3f" expectedPublicKey := ed25519.PublicKey(hexDecode(t, expectedPublicString)) assert.Equal(t, "52e3b8e73279d6ebdd62a5016e2725ff284f569665eb92ccb145d83817a02997", sv.keyID) assert.Equal(t, expectedPublicKey, sv.public) assert.Nil(t, sv.private) } func TestLoadED25519KeyFromFile(t *testing.T) { t.Run("ED25519 public key", func(t *testing.T) { key, err := LoadED25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub")) assert.Nil(t, err) assert.Equal(t, "52e3b8e73279d6ebdd62a5016e2725ff284f569665eb92ccb145d83817a02997", key.KeyID) assert.Equal(t, "3f586ce67329419fb0081bd995914e866a7205da463d593b3b490eab2b27fd3f", key.KeyVal.Public) assert.Equal(t, "ed25519", key.Scheme) assert.Equal(t, ED25519KeyType, key.KeyType) }) t.Run("ED25519 private key", func(t *testing.T) { key, err := LoadED25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key")) assert.Nil(t, err) assert.Equal(t, "52e3b8e73279d6ebdd62a5016e2725ff284f569665eb92ccb145d83817a02997", key.KeyID) assert.Equal(t, "3f586ce67329419fb0081bd995914e866a7205da463d593b3b490eab2b27fd3f", key.KeyVal.Public) assert.Equal(t, "66f6ebad4aeb949b91c84c9cfd6ee351fc4fd544744bab6e30fb400ba13c6e9a", key.KeyVal.Private) assert.Equal(t, "ed25519", key.Scheme) assert.Equal(t, ED25519KeyType, key.KeyType) }) t.Run("invalid path", func(t *testing.T) { _, err := LoadED25519KeyFromFile(filepath.Join("test-data", "invalid")) assert.ErrorContains(t, err, "unable to load ED25519 key from file") }) } func TestED25519SignerVerifierSign(t *testing.T) { key, err := LoadED25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key")) if err != nil { t.Fatal(err) } sv, err := NewED25519SignerVerifierFromSSLibKey(key) if err != nil { t.Error(err) } message := []byte("test message") signature, err := sv.Sign(context.Background(), message) if err != nil { t.Error(err) } expectedSignature := []byte{0x80, 0x72, 0xb4, 0x31, 0xc5, 0xa3, 0x7e, 0xc, 0xf3, 0x91, 0x22, 0x3, 0x60, 0xbf, 0x92, 0xa4, 0x46, 0x31, 0x84, 0x83, 0xf1, 0x31, 0x3, 0xdc, 0xbc, 0x5, 0x6f, 0xab, 0x84, 0xe4, 0xdc, 0xe9, 0xf5, 0x1c, 0xa9, 0xb3, 0x95, 0xa5, 0xa0, 0x16, 0xd3, 0xaa, 0x4d, 0xe7, 0xde, 0xaf, 0xc2, 0x5e, 0x1e, 0x9a, 0x9d, 0xc8, 0xb2, 0x5c, 0x1c, 0x68, 0xf7, 0x28, 0xb4, 0x1, 0x4d, 0x9f, 0xc8, 0x4} assert.Equal(t, expectedSignature, signature) key, err = LoadED25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub")) if err != nil { t.Fatal(err) } sv, err = NewED25519SignerVerifierFromSSLibKey(key) if err != nil { t.Error(err) } _, err = sv.Sign(context.Background(), message) assert.ErrorIs(t, err, ErrNotPrivateKey) } func TestED25519SignerVerifierVerify(t *testing.T) { key, err := LoadED25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub")) if err != nil { t.Fatal(err) } sv, err := NewED25519SignerVerifierFromSSLibKey(key) if err != nil { t.Error(err) } message := []byte("test message") signature := []byte{0x80, 0x72, 0xb4, 0x31, 0xc5, 0xa3, 0x7e, 0xc, 0xf3, 0x91, 0x22, 0x3, 0x60, 0xbf, 0x92, 0xa4, 0x46, 0x31, 0x84, 0x83, 0xf1, 0x31, 0x3, 0xdc, 0xbc, 0x5, 0x6f, 0xab, 0x84, 0xe4, 0xdc, 0xe9, 0xf5, 0x1c, 0xa9, 0xb3, 0x95, 0xa5, 0xa0, 0x16, 0xd3, 0xaa, 0x4d, 0xe7, 0xde, 0xaf, 0xc2, 0x5e, 0x1e, 0x9a, 0x9d, 0xc8, 0xb2, 0x5c, 0x1c, 0x68, 0xf7, 0x28, 0xb4, 0x1, 0x4d, 0x9f, 0xc8, 0x4} assert.Nil(t, sv.Verify(context.Background(), message, signature)) message = []byte("corrupted message") err = sv.Verify(context.Background(), message, signature) assert.ErrorIs(t, err, ErrSignatureVerificationFailed) } func TestED25519SignerVerifierWithDSSEEnvelope(t *testing.T) { key, err := LoadED25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key")) if err != nil { t.Fatal(err) } sv, err := NewED25519SignerVerifierFromSSLibKey(key) if err != nil { t.Fatal(err) } payloadType := "application/vnd.dsse+json" payload := []byte("test message") es, err := dsse.NewEnvelopeSigner(sv) if err != nil { t.Error(err) } env, err := es.SignPayload(context.Background(), payloadType, payload) if err != nil { t.Error(err) } assert.Equal(t, "52e3b8e73279d6ebdd62a5016e2725ff284f569665eb92ccb145d83817a02997", env.Signatures[0].KeyID) envPayload, err := env.DecodeB64Payload() assert.Equal(t, payload, envPayload) assert.Nil(t, err) key, err = LoadED25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub")) if err != nil { t.Fatal(err) } sv, err = NewED25519SignerVerifierFromSSLibKey(key) if err != nil { t.Fatal(err) } ev, err := dsse.NewEnvelopeVerifier(sv) if err != nil { t.Error(err) } acceptedKeys, err := ev.Verify(context.Background(), env) assert.Nil(t, err) assert.Equal(t, "52e3b8e73279d6ebdd62a5016e2725ff284f569665eb92ccb145d83817a02997", acceptedKeys[0].KeyID) } func TestED25519SignerVerifierWithMetablockFile(t *testing.T) { key, err := LoadED25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub")) if err != nil { t.Fatal(err) } sv, err := NewED25519SignerVerifierFromSSLibKey(key) if err != nil { t.Fatal(err) } metadataBytes, err := os.ReadFile(filepath.Join("test-data", "test-ed25519.52e3b8e7.link")) if err != nil { t.Fatal(err) } mb := struct { Signatures []struct { KeyID string `json:"keyid"` Sig string `json:"sig"` } `json:"signatures"` Signed any `json:"signed"` }{} if err := json.Unmarshal(metadataBytes, &mb); err != nil { t.Fatal(err) } assert.Equal(t, "4c8b7605a9195d4ddba54493bbb5257a9836c1d16056a027fd77e97b95a4f3e36f8bc3c9c9960387d68187760b3072a30c44f992c5bf8f7497c303a3b0a32403", mb.Signatures[0].Sig) assert.Equal(t, sv.keyID, mb.Signatures[0].KeyID) encodedBytes, err := cjson.EncodeCanonical(mb.Signed) if err != nil { t.Fatal(err) } decodedSig := hexDecode(t, mb.Signatures[0].Sig) err = sv.Verify(context.Background(), encodedBytes, decodedSig) assert.Nil(t, err) } go-securesystemslib-0.8.0/signerverifier/rsa.go000066400000000000000000000106571454036212700217030ustar00rootroot00000000000000package signerverifier import ( "context" "crypto" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "fmt" "os" "strings" ) const ( RSAKeyType = "rsa" RSAKeyScheme = "rsassa-pss-sha256" RSAPrivateKeyPEM = "RSA PRIVATE KEY" ) // RSAPSSSignerVerifier is a dsse.SignerVerifier compliant interface to sign and // verify signatures using RSA keys following the RSA-PSS scheme. type RSAPSSSignerVerifier struct { keyID string private *rsa.PrivateKey public *rsa.PublicKey } // NewRSAPSSSignerVerifierFromSSLibKey creates an RSAPSSSignerVerifier from an // SSLibKey. func NewRSAPSSSignerVerifierFromSSLibKey(key *SSLibKey) (*RSAPSSSignerVerifier, error) { if len(key.KeyVal.Public) == 0 { return nil, ErrInvalidKey } _, publicParsedKey, err := decodeAndParsePEM([]byte(key.KeyVal.Public)) if err != nil { return nil, fmt.Errorf("unable to create RSA-PSS signerverifier: %w", err) } if len(key.KeyVal.Private) > 0 { _, privateParsedKey, err := decodeAndParsePEM([]byte(key.KeyVal.Private)) if err != nil { return nil, fmt.Errorf("unable to create RSA-PSS signerverifier: %w", err) } return &RSAPSSSignerVerifier{ keyID: key.KeyID, public: publicParsedKey.(*rsa.PublicKey), private: privateParsedKey.(*rsa.PrivateKey), }, nil } return &RSAPSSSignerVerifier{ keyID: key.KeyID, public: publicParsedKey.(*rsa.PublicKey), private: nil, }, nil } // Sign creates a signature for `data`. func (sv *RSAPSSSignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) { if sv.private == nil { return nil, ErrNotPrivateKey } hashedData := hashBeforeSigning(data, sha256.New()) return rsa.SignPSS(rand.Reader, sv.private, crypto.SHA256, hashedData, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) } // Verify verifies the `sig` value passed in against `data`. func (sv *RSAPSSSignerVerifier) Verify(ctx context.Context, data []byte, sig []byte) error { hashedData := hashBeforeSigning(data, sha256.New()) if err := rsa.VerifyPSS(sv.public, crypto.SHA256, hashedData, sig, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}); err != nil { return ErrSignatureVerificationFailed } return nil } // KeyID returns the identifier of the key used to create the // RSAPSSSignerVerifier instance. func (sv *RSAPSSSignerVerifier) KeyID() (string, error) { return sv.keyID, nil } // Public returns the public portion of the key used to create the // RSAPSSSignerVerifier instance. func (sv *RSAPSSSignerVerifier) Public() crypto.PublicKey { return sv.public } // LoadRSAPSSKeyFromFile returns an SSLibKey instance for an RSA key stored in a // file. func LoadRSAPSSKeyFromFile(path string) (*SSLibKey, error) { contents, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("unable to load RSA key from file: %w", err) } return LoadRSAPSSKeyFromBytes(contents) } // LoadRSAPSSKeyFromBytes is a function that takes a byte array as input. This byte array should represent a PEM encoded RSA key, as PEM encoding is required. // The function returns an SSLibKey instance, which is a struct that holds the key data. func LoadRSAPSSKeyFromBytes(contents []byte) (*SSLibKey, error) { pemData, keyObj, err := decodeAndParsePEM(contents) if err != nil { return nil, fmt.Errorf("unable to load RSA key from file: %w", err) } key := &SSLibKey{ KeyType: RSAKeyType, Scheme: RSAKeyScheme, KeyIDHashAlgorithms: KeyIDHashAlgorithms, KeyVal: KeyVal{}, } pubKeyBytes, err := marshalAndGeneratePEM(keyObj) if err != nil { return nil, fmt.Errorf("unable to load RSA key from file: %w", err) } key.KeyVal.Public = strings.TrimSpace(string(pubKeyBytes)) if _, ok := keyObj.(*rsa.PrivateKey); ok { key.KeyVal.Private = strings.TrimSpace(string(generatePEMBlock(pemData.Bytes, RSAPrivateKeyPEM))) } if len(key.KeyID) == 0 { keyID, err := calculateKeyID(key) if err != nil { return nil, fmt.Errorf("unable to load RSA key from file: %w", err) } key.KeyID = keyID } return key, nil } func marshalAndGeneratePEM(key interface{}) ([]byte, error) { var pubKeyBytes []byte var err error switch k := key.(type) { case *rsa.PublicKey: pubKeyBytes, err = x509.MarshalPKIXPublicKey(k) case *rsa.PrivateKey: pubKeyBytes, err = x509.MarshalPKIXPublicKey(k.Public()) default: return nil, fmt.Errorf("unexpected key type: %T", k) } if err != nil { return nil, err } return generatePEMBlock(pubKeyBytes, PublicKeyPEM), nil } go-securesystemslib-0.8.0/signerverifier/rsa_test.go000066400000000000000000000237271454036212700227440ustar00rootroot00000000000000package signerverifier import ( "context" "crypto/rsa" "encoding/json" "os" "path/filepath" "testing" "github.com/secure-systems-lab/go-securesystemslib/cjson" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/stretchr/testify/assert" ) func TestNewRSAPSSSignerVerifierFromSSLibKey(t *testing.T) { key, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key.pub")) if err != nil { t.Error(err) } sv, err := NewRSAPSSSignerVerifierFromSSLibKey(key) if err != nil { t.Error(err) } expectedPublicString := "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA04egZRic+dZMVtiQc56D\nejU4FF1q3aOkUKnD+Q4lTbj1zp6ODKJTcktupmrad68jqtMiSGG8he6ELFs377q8\nbbgEUMWgAf+06Q8oFvUSfOXzZNFI7H5SMPOJY5aDWIMIEZ8DlcO7TfkA7D3iAEJX\nxxTOVS3UAIk5umO7Y7t7yXr8O/C4u78krGazCnoblcekMLJZV4O/5BloWNAe/B1c\nvZdaZUf3brD4ZZrxEtXw/tefhn1aHsSUajVW2wwjSpKhqj7Z0XS3bDS3T95/3xsN\n6+hlS6A7rJfiWpKIRHj0vh2SXLDmmhQl1In8TD/aiycTUyWcBRHVPlYFgYPt6SaT\nVQSgMzSxC43/2fINb2fyt8SbUHJ3Ct+mzRzd/1AQikWhBdstJLxInewzjYE/sb+c\n2CmCxMPQG2BwmAWXaaumeJcXVPBlMgAcjMatM8bPByTbXpKDnQslOE7g/gswDIwn\nEm53T13mZzYUvbLJ0q3aljZVLIC3IZn3ZwA2yCWchBkVAgMBAAE=\n-----END PUBLIC KEY-----" _, expectedPublicKey, err := decodeAndParsePEM([]byte(expectedPublicString)) assert.Nil(t, err) assert.Equal(t, "4e8d20af09fcaed6c388a186427f94a5f7ff5591ec295f4aab2cff49ffe39e9b", sv.keyID) assert.Equal(t, expectedPublicKey.(*rsa.PublicKey), sv.public) assert.Nil(t, sv.private) } func TestLoadRSAPSSKeyFromFile(t *testing.T) { t.Run("RSA public key", func(t *testing.T) { key, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key.pub")) assert.Nil(t, err) assert.Equal(t, "4e8d20af09fcaed6c388a186427f94a5f7ff5591ec295f4aab2cff49ffe39e9b", key.KeyID) assert.Equal(t, "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA04egZRic+dZMVtiQc56D\nejU4FF1q3aOkUKnD+Q4lTbj1zp6ODKJTcktupmrad68jqtMiSGG8he6ELFs377q8\nbbgEUMWgAf+06Q8oFvUSfOXzZNFI7H5SMPOJY5aDWIMIEZ8DlcO7TfkA7D3iAEJX\nxxTOVS3UAIk5umO7Y7t7yXr8O/C4u78krGazCnoblcekMLJZV4O/5BloWNAe/B1c\nvZdaZUf3brD4ZZrxEtXw/tefhn1aHsSUajVW2wwjSpKhqj7Z0XS3bDS3T95/3xsN\n6+hlS6A7rJfiWpKIRHj0vh2SXLDmmhQl1In8TD/aiycTUyWcBRHVPlYFgYPt6SaT\nVQSgMzSxC43/2fINb2fyt8SbUHJ3Ct+mzRzd/1AQikWhBdstJLxInewzjYE/sb+c\n2CmCxMPQG2BwmAWXaaumeJcXVPBlMgAcjMatM8bPByTbXpKDnQslOE7g/gswDIwn\nEm53T13mZzYUvbLJ0q3aljZVLIC3IZn3ZwA2yCWchBkVAgMBAAE=\n-----END PUBLIC KEY-----", key.KeyVal.Public) assert.Equal(t, RSAKeyScheme, key.Scheme) assert.Equal(t, RSAKeyType, key.KeyType) }) t.Run("RSA private key", func(t *testing.T) { key, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key")) assert.Nil(t, err) assert.Equal(t, "4e8d20af09fcaed6c388a186427f94a5f7ff5591ec295f4aab2cff49ffe39e9b", key.KeyID) assert.Equal(t, "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA04egZRic+dZMVtiQc56D\nejU4FF1q3aOkUKnD+Q4lTbj1zp6ODKJTcktupmrad68jqtMiSGG8he6ELFs377q8\nbbgEUMWgAf+06Q8oFvUSfOXzZNFI7H5SMPOJY5aDWIMIEZ8DlcO7TfkA7D3iAEJX\nxxTOVS3UAIk5umO7Y7t7yXr8O/C4u78krGazCnoblcekMLJZV4O/5BloWNAe/B1c\nvZdaZUf3brD4ZZrxEtXw/tefhn1aHsSUajVW2wwjSpKhqj7Z0XS3bDS3T95/3xsN\n6+hlS6A7rJfiWpKIRHj0vh2SXLDmmhQl1In8TD/aiycTUyWcBRHVPlYFgYPt6SaT\nVQSgMzSxC43/2fINb2fyt8SbUHJ3Ct+mzRzd/1AQikWhBdstJLxInewzjYE/sb+c\n2CmCxMPQG2BwmAWXaaumeJcXVPBlMgAcjMatM8bPByTbXpKDnQslOE7g/gswDIwn\nEm53T13mZzYUvbLJ0q3aljZVLIC3IZn3ZwA2yCWchBkVAgMBAAE=\n-----END PUBLIC KEY-----", key.KeyVal.Public) expectedPrivateKey := "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----" assert.Equal(t, expectedPrivateKey, key.KeyVal.Private) assert.Equal(t, RSAKeyScheme, key.Scheme) assert.Equal(t, RSAKeyType, key.KeyType) }) t.Run("invalid path", func(t *testing.T) { _, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "invalid")) assert.ErrorContains(t, err, "unable to load RSA key from file") }) } func TestRSAPSSSignerVerifierSignAndVerify(t *testing.T) { t.Run("using valid key", func(t *testing.T) { key, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key")) if err != nil { t.Error(err) } sv, err := NewRSAPSSSignerVerifierFromSSLibKey(key) if err != nil { t.Error(err) } message := []byte("test message") signature, err := sv.Sign(context.Background(), message) assert.Nil(t, err) err = sv.Verify(context.Background(), message, signature) assert.Nil(t, err) }) t.Run("using invalid key", func(t *testing.T) { key, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key.pub")) if err != nil { t.Error(err) } sv, err := NewRSAPSSSignerVerifierFromSSLibKey(key) if err != nil { t.Error(err) } message := []byte("test message") _, err = sv.Sign(context.Background(), message) assert.ErrorIs(t, err, ErrNotPrivateKey) }) } func TestRSAPSSSignerVerifierWithDSSEEnvelope(t *testing.T) { key, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key")) if err != nil { t.Fatal(err) } sv, err := NewRSAPSSSignerVerifierFromSSLibKey(key) if err != nil { t.Fatal(err) } payloadType := "application/vnd.dsse+json" payload := []byte("test message") es, err := dsse.NewEnvelopeSigner(sv) if err != nil { t.Error(err) } env, err := es.SignPayload(context.Background(), payloadType, payload) if err != nil { t.Error(err) } assert.Equal(t, "4e8d20af09fcaed6c388a186427f94a5f7ff5591ec295f4aab2cff49ffe39e9b", env.Signatures[0].KeyID) envPayload, err := env.DecodeB64Payload() assert.Equal(t, payload, envPayload) assert.Nil(t, err) key, err = LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key.pub")) if err != nil { t.Fatal(err) } sv, err = NewRSAPSSSignerVerifierFromSSLibKey(key) if err != nil { t.Fatal(err) } ev, err := dsse.NewEnvelopeVerifier(sv) if err != nil { t.Error(err) } acceptedKeys, err := ev.Verify(context.Background(), env) assert.Nil(t, err) assert.Equal(t, "4e8d20af09fcaed6c388a186427f94a5f7ff5591ec295f4aab2cff49ffe39e9b", acceptedKeys[0].KeyID) } func TestRSAPSSSignerVerifierWithMetablockFile(t *testing.T) { key, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key.pub")) if err != nil { t.Fatal(err) } sv, err := NewRSAPSSSignerVerifierFromSSLibKey(key) if err != nil { t.Fatal(err) } metadataBytes, err := os.ReadFile(filepath.Join("test-data", "test-rsa.4e8d20af.link")) if err != nil { t.Fatal(err) } mb := struct { Signatures []struct { KeyID string `json:"keyid"` Sig string `json:"sig"` } `json:"signatures"` Signed any `json:"signed"` }{} if err := json.Unmarshal(metadataBytes, &mb); err != nil { t.Fatal(err) } assert.Equal(t, "8958e5be66ee4352880a531bd097d1727adcc78e66b4faeb4a2cd6ad073dcb84f9a34e8156af39a7144cb5cd925325a18ccd4f0b2f981d6ff82655a7d63210d36655c50a0bf24e4839c10430a040dd6189d04fabec90eae4314c75ae2d585da17a56aaf6755e613a3a6a471ad2eddbb24504848e34f9ac163660f8ab80d7701bfa1189578a59597b3809ee62a70a7cc9545cfa65e23018fa442a45279b9fcf9d80bc92df711bfcfe16e3eae1bcf61b3286c1f0bdda17bc28bfab5b736bdcac4a38e31db1d0e0f56a2853b1b451650305f040a3425c3be47125700e92ef82c5a91a040b5e70ab7f6ebbe037ae1a6835044b5699748037e2e39a55a420c41cd9fa6e16868776367e3620e7d28eb9d8a3d710bdc98d488df1a9947d2ec8400f3c6209e8ca587cbffa30ceb3be98105e03182aab1bbb3c4e2560d99f0b09c012df2271f273ac70a6abb185abe11d559b118dca616417fa9205e74ab58e89ffd8b965da304ae9dc9cf6ffac4838b7c5375d6c2057a61cb286f06ad3b02a49c3af6178", mb.Signatures[0].Sig) assert.Equal(t, sv.keyID, mb.Signatures[0].KeyID) encodedBytes, err := cjson.EncodeCanonical(mb.Signed) if err != nil { t.Fatal(err) } decodedSig := hexDecode(t, mb.Signatures[0].Sig) err = sv.Verify(context.Background(), encodedBytes, decodedSig) assert.Nil(t, err) } go-securesystemslib-0.8.0/signerverifier/signerverifier.go000066400000000000000000000021561454036212700241340ustar00rootroot00000000000000package signerverifier import ( "errors" ) var KeyIDHashAlgorithms = []string{"sha256", "sha512"} var ( ErrNotPrivateKey = errors.New("loaded key is not a private key") ErrSignatureVerificationFailed = errors.New("failed to verify signature") ErrUnknownKeyType = errors.New("unknown key type") ErrInvalidThreshold = errors.New("threshold is either less than 1 or greater than number of provided public keys") ErrInvalidKey = errors.New("key object has no value") ) const ( PublicKeyPEM = "PUBLIC KEY" PrivateKeyPEM = "PRIVATE KEY" ) type SSLibKey struct { KeyIDHashAlgorithms []string `json:"keyid_hash_algorithms"` KeyType string `json:"keytype"` KeyVal KeyVal `json:"keyval"` Scheme string `json:"scheme"` KeyID string `json:"keyid"` } type KeyVal struct { Private string `json:"private,omitempty"` Public string `json:"public,omitempty"` Certificate string `json:"certificate,omitempty"` Identity string `json:"identity,omitempty"` Issuer string `json:"issuer,omitempty"` } go-securesystemslib-0.8.0/signerverifier/test-data/000077500000000000000000000000001454036212700224445ustar00rootroot00000000000000go-securesystemslib-0.8.0/signerverifier/test-data/ecdsa-test-key000066400000000000000000000011651454036212700252140ustar00rootroot00000000000000{"keytype": "ecdsa", "scheme": "ecdsa-sha2-nistp256", "keyid": "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", "keyval": {"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1\nXM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END PUBLIC KEY-----", "private": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIAo6DxXlgqYy+TkvocIOyWlqA3KVtp6dlSY7lS3kkeEMoAoGCCqGSM49\nAwEHoUQDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1XM36oXymJ9wxpM68nCqkrZCV\nnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END EC PRIVATE KEY-----"}, "keyid_hash_algorithms": ["sha256", "sha512"]} go-securesystemslib-0.8.0/signerverifier/test-data/ecdsa-test-key.pub000077500000000000000000000004631454036212700260040ustar00rootroot00000000000000{"keytype": "ecdsa", "scheme": "ecdsa-sha2-nistp256", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1\nXM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END PUBLIC KEY-----"}} go-securesystemslib-0.8.0/signerverifier/test-data/ed25519-test-key000066400000000000000000000005211454036212700251260ustar00rootroot00000000000000{"keytype": "ed25519", "scheme": "ed25519", "keyid": "52e3b8e73279d6ebdd62a5016e2725ff284f569665eb92ccb145d83817a02997", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "3f586ce67329419fb0081bd995914e866a7205da463d593b3b490eab2b27fd3f", "private": "66f6ebad4aeb949b91c84c9cfd6ee351fc4fd544744bab6e30fb400ba13c6e9a"}} go-securesystemslib-0.8.0/signerverifier/test-data/ed25519-test-key.pub000066400000000000000000000002651454036212700257200ustar00rootroot00000000000000{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "3f586ce67329419fb0081bd995914e866a7205da463d593b3b490eab2b27fd3f"}} go-securesystemslib-0.8.0/signerverifier/test-data/rsa-test-key000066400000000000000000000046331454036212700247250ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- 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 -----END RSA PRIVATE KEY----- go-securesystemslib-0.8.0/signerverifier/test-data/rsa-test-key.pub000066400000000000000000000011611454036212700255030ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA04egZRic+dZMVtiQc56D ejU4FF1q3aOkUKnD+Q4lTbj1zp6ODKJTcktupmrad68jqtMiSGG8he6ELFs377q8 bbgEUMWgAf+06Q8oFvUSfOXzZNFI7H5SMPOJY5aDWIMIEZ8DlcO7TfkA7D3iAEJX xxTOVS3UAIk5umO7Y7t7yXr8O/C4u78krGazCnoblcekMLJZV4O/5BloWNAe/B1c vZdaZUf3brD4ZZrxEtXw/tefhn1aHsSUajVW2wwjSpKhqj7Z0XS3bDS3T95/3xsN 6+hlS6A7rJfiWpKIRHj0vh2SXLDmmhQl1In8TD/aiycTUyWcBRHVPlYFgYPt6SaT VQSgMzSxC43/2fINb2fyt8SbUHJ3Ct+mzRzd/1AQikWhBdstJLxInewzjYE/sb+c 2CmCxMPQG2BwmAWXaaumeJcXVPBlMgAcjMatM8bPByTbXpKDnQslOE7g/gswDIwn Em53T13mZzYUvbLJ0q3aljZVLIC3IZn3ZwA2yCWchBkVAgMBAAE= -----END PUBLIC KEY----- go-securesystemslib-0.8.0/signerverifier/test-data/test-ecdsa.98adf386.link000066400000000000000000000006441454036212700265360ustar00rootroot00000000000000{ "signatures": [ { "keyid": "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", "sig": "304502201fbb03c0937504182a48c66f9218bdcb2e99a07ada273e92e5e543867f98c8d7022100dbfa7bbf74fd76d76c1d08676419cba85bbd81dfb000f3ac6a786693ddc508f5" } ], "signed": { "_type": "link", "byproducts": {}, "command": [], "environment": {}, "materials": {}, "name": "test-ecdsa", "products": {} } }go-securesystemslib-0.8.0/signerverifier/test-data/test-ed25519.52e3b8e7.link000066400000000000000000000006301454036212700264400ustar00rootroot00000000000000{ "signatures": [ { "keyid": "52e3b8e73279d6ebdd62a5016e2725ff284f569665eb92ccb145d83817a02997", "sig": "4c8b7605a9195d4ddba54493bbb5257a9836c1d16056a027fd77e97b95a4f3e36f8bc3c9c9960387d68187760b3072a30c44f992c5bf8f7497c303a3b0a32403" } ], "signed": { "_type": "link", "byproducts": {}, "command": [], "environment": {}, "materials": {}, "name": "test-ed25519", "products": {} } }go-securesystemslib-0.8.0/signerverifier/test-data/test-rsa.4e8d20af.link000066400000000000000000000020241454036212700262770ustar00rootroot00000000000000{ "signatures": [ { "keyid": "4e8d20af09fcaed6c388a186427f94a5f7ff5591ec295f4aab2cff49ffe39e9b", "sig": "8958e5be66ee4352880a531bd097d1727adcc78e66b4faeb4a2cd6ad073dcb84f9a34e8156af39a7144cb5cd925325a18ccd4f0b2f981d6ff82655a7d63210d36655c50a0bf24e4839c10430a040dd6189d04fabec90eae4314c75ae2d585da17a56aaf6755e613a3a6a471ad2eddbb24504848e34f9ac163660f8ab80d7701bfa1189578a59597b3809ee62a70a7cc9545cfa65e23018fa442a45279b9fcf9d80bc92df711bfcfe16e3eae1bcf61b3286c1f0bdda17bc28bfab5b736bdcac4a38e31db1d0e0f56a2853b1b451650305f040a3425c3be47125700e92ef82c5a91a040b5e70ab7f6ebbe037ae1a6835044b5699748037e2e39a55a420c41cd9fa6e16868776367e3620e7d28eb9d8a3d710bdc98d488df1a9947d2ec8400f3c6209e8ca587cbffa30ceb3be98105e03182aab1bbb3c4e2560d99f0b09c012df2271f273ac70a6abb185abe11d559b118dca616417fa9205e74ab58e89ffd8b965da304ae9dc9cf6ffac4838b7c5375d6c2057a61cb286f06ad3b02a49c3af6178" } ], "signed": { "_type": "link", "byproducts": {}, "command": [], "environment": {}, "materials": {}, "name": "test-rsa", "products": {} } }go-securesystemslib-0.8.0/signerverifier/utils.go000066400000000000000000000075601454036212700222550ustar00rootroot00000000000000package signerverifier import ( "crypto/sha256" "crypto/x509" "encoding/hex" "encoding/json" "encoding/pem" "errors" "hash" "testing" "github.com/secure-systems-lab/go-securesystemslib/cjson" ) /* Credits: Parts of this file were originally authored for in-toto-golang. */ var ( // ErrNoPEMBlock gets triggered when there is no PEM block in the provided file ErrNoPEMBlock = errors.New("failed to decode the data as PEM block (are you sure this is a pem file?)") // ErrFailedPEMParsing gets returned when PKCS1, PKCS8 or PKIX key parsing fails ErrFailedPEMParsing = errors.New("failed parsing the PEM block: unsupported PEM type") ) // LoadKeyFromSSLibBytes returns a pointer to a Key instance created from the // contents of the bytes. The key contents are expected to be in the custom // securesystemslib format. func LoadKeyFromSSLibBytes(contents []byte) (*SSLibKey, error) { var key *SSLibKey if err := json.Unmarshal(contents, &key); err != nil { return LoadRSAPSSKeyFromBytes(contents) } if len(key.KeyID) == 0 { keyID, err := calculateKeyID(key) if err != nil { return nil, err } key.KeyID = keyID } return key, nil } func calculateKeyID(k *SSLibKey) (string, error) { key := map[string]any{ "keytype": k.KeyType, "scheme": k.Scheme, "keyid_hash_algorithms": k.KeyIDHashAlgorithms, "keyval": map[string]string{ "public": k.KeyVal.Public, }, } canonical, err := cjson.EncodeCanonical(key) if err != nil { return "", err } digest := sha256.Sum256(canonical) return hex.EncodeToString(digest[:]), nil } /* generatePEMBlock creates a PEM block from scratch via the keyBytes and the pemType. If successful it returns a PEM block as []byte slice. This function should always succeed, if keyBytes is empty the PEM block will have an empty byte block. Therefore only header and footer will exist. */ func generatePEMBlock(keyBytes []byte, pemType string) []byte { // construct PEM block pemBlock := &pem.Block{ Type: pemType, Headers: nil, Bytes: keyBytes, } return pem.EncodeToMemory(pemBlock) } /* decodeAndParsePEM receives potential PEM bytes decodes them via pem.Decode and pushes them to parseKey. If any error occurs during this process, the function will return nil and an error (either ErrFailedPEMParsing or ErrNoPEMBlock). On success it will return the decoded pemData, the key object interface and nil as error. We need the decoded pemData, because LoadKey relies on decoded pemData for operating system interoperability. */ func decodeAndParsePEM(pemBytes []byte) (*pem.Block, any, error) { // pem.Decode returns the parsed pem block and a rest. // The rest is everything, that could not be parsed as PEM block. // Therefore we can drop this via using the blank identifier "_" data, _ := pem.Decode(pemBytes) if data == nil { return nil, nil, ErrNoPEMBlock } // Try to load private key, if this fails try to load // key as public key key, err := parsePEMKey(data.Bytes) if err != nil { return nil, nil, err } return data, key, nil } /* parseKey tries to parse a PEM []byte slice. Using the following standards in the given order: - PKCS8 - PKCS1 - PKIX On success it returns the parsed key and nil. On failure it returns nil and the error ErrFailedPEMParsing */ func parsePEMKey(data []byte) (any, error) { key, err := x509.ParsePKCS8PrivateKey(data) if err == nil { return key, nil } key, err = x509.ParsePKCS1PrivateKey(data) if err == nil { return key, nil } key, err = x509.ParsePKIXPublicKey(data) if err == nil { return key, nil } key, err = x509.ParseECPrivateKey(data) if err == nil { return key, nil } return nil, ErrFailedPEMParsing } func hashBeforeSigning(data []byte, h hash.Hash) []byte { h.Write(data) return h.Sum(nil) } func hexDecode(t *testing.T, data string) []byte { t.Helper() b, err := hex.DecodeString(data) if err != nil { t.Fatal(err) } return b } go-securesystemslib-0.8.0/signerverifier/utils_test.go000066400000000000000000000114751454036212700233140ustar00rootroot00000000000000package signerverifier import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" ) func TestLoadKeyFromSSLibBytes(t *testing.T) { t.Run("RSA public key", func(t *testing.T) { contents, err := os.ReadFile(filepath.Join("test-data", "rsa-test-key.pub")) if err != nil { t.Fatal(err) } key, err := LoadKeyFromSSLibBytes(contents) assert.Nil(t, err) assert.Equal(t, "4e8d20af09fcaed6c388a186427f94a5f7ff5591ec295f4aab2cff49ffe39e9b", key.KeyID) assert.Equal(t, "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA04egZRic+dZMVtiQc56D\nejU4FF1q3aOkUKnD+Q4lTbj1zp6ODKJTcktupmrad68jqtMiSGG8he6ELFs377q8\nbbgEUMWgAf+06Q8oFvUSfOXzZNFI7H5SMPOJY5aDWIMIEZ8DlcO7TfkA7D3iAEJX\nxxTOVS3UAIk5umO7Y7t7yXr8O/C4u78krGazCnoblcekMLJZV4O/5BloWNAe/B1c\nvZdaZUf3brD4ZZrxEtXw/tefhn1aHsSUajVW2wwjSpKhqj7Z0XS3bDS3T95/3xsN\n6+hlS6A7rJfiWpKIRHj0vh2SXLDmmhQl1In8TD/aiycTUyWcBRHVPlYFgYPt6SaT\nVQSgMzSxC43/2fINb2fyt8SbUHJ3Ct+mzRzd/1AQikWhBdstJLxInewzjYE/sb+c\n2CmCxMPQG2BwmAWXaaumeJcXVPBlMgAcjMatM8bPByTbXpKDnQslOE7g/gswDIwn\nEm53T13mZzYUvbLJ0q3aljZVLIC3IZn3ZwA2yCWchBkVAgMBAAE=\n-----END PUBLIC KEY-----", key.KeyVal.Public) assert.Equal(t, RSAKeyScheme, key.Scheme) assert.Equal(t, RSAKeyType, key.KeyType) }) t.Run("RSA private key", func(t *testing.T) { contents, err := os.ReadFile(filepath.Join("test-data", "rsa-test-key")) if err != nil { t.Fatal(err) } key, err := LoadKeyFromSSLibBytes(contents) assert.Nil(t, err) assert.Equal(t, "4e8d20af09fcaed6c388a186427f94a5f7ff5591ec295f4aab2cff49ffe39e9b", key.KeyID) assert.Equal(t, "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA04egZRic+dZMVtiQc56D\nejU4FF1q3aOkUKnD+Q4lTbj1zp6ODKJTcktupmrad68jqtMiSGG8he6ELFs377q8\nbbgEUMWgAf+06Q8oFvUSfOXzZNFI7H5SMPOJY5aDWIMIEZ8DlcO7TfkA7D3iAEJX\nxxTOVS3UAIk5umO7Y7t7yXr8O/C4u78krGazCnoblcekMLJZV4O/5BloWNAe/B1c\nvZdaZUf3brD4ZZrxEtXw/tefhn1aHsSUajVW2wwjSpKhqj7Z0XS3bDS3T95/3xsN\n6+hlS6A7rJfiWpKIRHj0vh2SXLDmmhQl1In8TD/aiycTUyWcBRHVPlYFgYPt6SaT\nVQSgMzSxC43/2fINb2fyt8SbUHJ3Ct+mzRzd/1AQikWhBdstJLxInewzjYE/sb+c\n2CmCxMPQG2BwmAWXaaumeJcXVPBlMgAcjMatM8bPByTbXpKDnQslOE7g/gswDIwn\nEm53T13mZzYUvbLJ0q3aljZVLIC3IZn3ZwA2yCWchBkVAgMBAAE=\n-----END PUBLIC KEY-----", key.KeyVal.Public) expectedPrivateKey := "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----" assert.Equal(t, expectedPrivateKey, key.KeyVal.Private) assert.Equal(t, RSAKeyScheme, key.Scheme) assert.Equal(t, RSAKeyType, key.KeyType) }) }