Skip to content

Commit

Permalink
implement pedigree verification
Browse files Browse the repository at this point in the history
Signed-off-by: Vivek Kumar Sahu <vivekkumarsahu650@gmail.com>
  • Loading branch information
viveksahu26 committed Nov 30, 2024
1 parent 87482c5 commit e9faa90
Show file tree
Hide file tree
Showing 10 changed files with 428 additions and 2 deletions.
18 changes: 18 additions & 0 deletions pkg/hshgrity/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package hshgrity

// type GitHubCommit struct {
// SHA string `json:"sha"`
// Commit CommitDetails `json:"commit"`
// }

// type CommitDetails struct {
// Author sbom.CommitAuthor `json:"author"`
// Committer sbom.CommitCommitter `json:"committer"`
// Message string `json:"message"`
// }

// type GitHubAuthor struct {
// Name string `json:"name"`
// Email string `json:"email"`
// Date string `json:"date"`
// }
149 changes: 149 additions & 0 deletions pkg/hshgrity/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package hshgrity

import (
"bufio"
"encoding/json"
"fmt"
"net/http"
"strings"

"github.com/interlynk-io/sbomqs/pkg/sbom"
)

// GetCalculatedHash constructs the URL for the checksum file and retrieves the hash
func GetCalculatedHash(purl string) ([]string, error) {
// Extract information from purl
purlParts := strings.Split(purl, "/")
if len(purlParts) < 4 {
return nil, fmt.Errorf("invalid purl format")
}
fmt.Println("purlParts: ", purlParts)

typeParts := strings.Split(purlParts[0], ":")
fmt.Println("repoParts: ", typeParts)
if len(typeParts) < 2 {
return nil, fmt.Errorf("invalid purl format")
}
org := purlParts[2]
// repo := repoParts[2]

repoParts := strings.Split(purlParts[3], "@")
if len(repoParts) < 2 {
return nil, fmt.Errorf("invalid purl format")
}
repo := repoParts[0]
version := repoParts[1]

// Remove the 'v' prefix from the version if it exists
if strings.HasPrefix(version, "v") {

Check failure on line 38 in pkg/hshgrity/utils.go

View workflow job for this annotation

GitHub Actions / lint

S1017: should replace this `if` statement with an unconditional `strings.TrimPrefix` (gosimple)
version = version[1:]
}

// Construct the URL for the checksum file
versionedChecksumURL := fmt.Sprintf("https://github.com/%s/%s/releases/download/v%s/%s_%s_checksums.txt", org, repo, version, repo, version)
fmt.Println("versionedChecksumURL : ", versionedChecksumURL)

// Attempt to download the versioned checksum file
hashes, err := downloadChecksumFile(versionedChecksumURL)
if err == nil {
return hashes, nil
}

// If the versioned checksum file is not found, construct the URL for the general checksum file
generalChecksumURL := fmt.Sprintf("https://github.com/%s/%s/releases/download/v%s/%s_checksums.txt", org, repo, version, repo)
fmt.Println("generalChecksumURL: ", generalChecksumURL)

// Attempt to download the general checksum file
hashes, err = downloadChecksumFile(generalChecksumURL)
if err != nil {
return nil, fmt.Errorf("error downloading checksum file: %v", err)
}

return hashes, nil
}

// downloadChecksumFile downloads the checksum file from the given URL and extracts all hashes
func downloadChecksumFile(url string) ([]string, error) {
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("error downloading checksum file: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("checksum file not found at URL: %s", url)
}

var hashes []string
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) > 0 {
hashes = append(hashes, parts[0])
}
}
fmt.Println("hashes: ", hashes)

if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error reading checksum file: %v", err)
}

return hashes, nil
}

func FetchCommitDetails(commitURL string) (*sbom.Commit, error) {
resp, err := http.Get(commitURL)
if err != nil {
return nil, fmt.Errorf("error fetching commit details: %v", err)
}
fmt.Println("resp.Body: ", resp.Body)
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("commit not found at URL: %s", commitURL)
}

var commit sbom.Commit
if err := json.NewDecoder(resp.Body).Decode(&commit); err != nil {
return nil, fmt.Errorf("error decoding commit details: %v", err)
}

return &commit, nil
}

func VerifyCommitDetails(sbomCommit sbom.GetCommit, githubCommit sbom.GetCommit) bool {
// fmt.Println("githubCommit.GetUID(): ", githubCommit.GetUID())
// fmt.Println("sbomCommit.GetUID(): ", sbomCommit.GetUID())

// fmt.Println("sbomCommit.GetUID(): ", sbomCommit.GetUID())
// fmt.Println("sbomCommit.GetUID(): ", sbomCommit.GetUID())

// fmt.Println("sbomCommit.GetUID(): ", sbomCommit.GetUID())
// fmt.Println("sbomCommit.GetUID(): ", sbomCommit.GetUID())

if sbomCommit.GetUID() != githubCommit.GetUID() {
fmt.Println("false1")
return false
}
fmt.Println("sbomCommit.GetComitAuthor().GetName() : ", sbomCommit.GetComitAuthor().GetName())
fmt.Println("githubCommit.GetComitAuthor().GetName() : ", githubCommit.GetComitAuthor().GetName())

fmt.Println("sbomCommit.GetComitAuthor().GetEmail() : ", sbomCommit.GetComitAuthor().GetEmail())
fmt.Println("githubCommit.GetComitAuthor().GetEmail() : ", githubCommit.GetComitAuthor().GetEmail())

fmt.Println("sbomCommit.GetComitAuthor().GetTimestamp() : ", sbomCommit.GetComitAuthor().GetTimestamp())
fmt.Println("githubCommit.GetComitAuthor().GetTimestamp() : ", githubCommit.GetComitAuthor().GetTimestamp())

if sbomCommit.GetComitAuthor().GetName() != githubCommit.GetComitAuthor().GetName() || sbomCommit.GetComitAuthor().GetEmail() != githubCommit.GetComitAuthor().GetEmail() || sbomCommit.GetComitAuthor().GetTimestamp() != githubCommit.GetComitAuthor().GetTimestamp() {
fmt.Println("false2")

return false
}
if sbomCommit.GetComitComiter().GetName() != githubCommit.GetComitComiter().GetName() || sbomCommit.GetComitComiter().GetEmail() != githubCommit.GetComitComiter().GetEmail() || sbomCommit.GetComitComiter().GetTimestamp() != githubCommit.GetComitComiter().GetTimestamp() {
fmt.Println("false3")

return false
}
return true
}
40 changes: 40 additions & 0 deletions pkg/sbom/cdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,44 @@ func (c *CdxDoc) parseSignature() {
}
}

func (c *CdxDoc) parsePedigree(comp *cydx.Component) Pedigree {
pedigree := Pedigree{}

if comp.Pedigree == nil {
return pedigree
}

if comp.Pedigree.Commits == nil {
return pedigree
}
commits := []GetCommit{}

for _, cmt := range *comp.Pedigree.Commits {
commit := Commit{}
commit.Uid = cmt.UID
commit.Url = cmt.URL
commit.Message = cmt.Message

author := CommitAuthor{}
author.Name = cmt.Author.Name
author.Email = cmt.Author.Email
author.Timestamp = cmt.Author.Timestamp
commit.ComitAuthor = author

committer := CommitCommitter{}
committer.Name = cmt.Committer.Name
committer.Email = cmt.Committer.Email
committer.Timestamp = cmt.Committer.Timestamp
commit.ComitComiter = committer

commits = append(commits, commit)

}
pedigree.Commits = commits

return pedigree
}

func (c *CdxDoc) requiredFields() bool {
if c.doc == nil {
c.addToLogs("cdx doc is not parsable")
Expand Down Expand Up @@ -398,6 +436,8 @@ func copyC(cdxc *cydx.Component, c *CdxDoc) *Component {
if cdxc.Name == c.PrimaryComponent.Name {
nc.PrimaryCompt = c.PrimaryComponent
}
nc.Pedigrees = c.parsePedigree(cdxc)

nc.ID = cdxc.BOMRef
return nc
}
Expand Down
52 changes: 52 additions & 0 deletions pkg/sbom/commit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2024 Interlynk.io
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sbom

type GetCommit interface {
GetUID() string
GetURL() string
GetComitAuthor() CommitAuthor
GetComitComiter() CommitCommitter
GetMessage() string
}

// nolint
type Commit struct {
Uid string `json:"sha"`
Url string `json:"url"`
ComitAuthor CommitAuthor `json:"author"`
ComitComiter CommitCommitter `json:"committer"`
Message string `json:"message"`
}

func (c Commit) GetUID() string {
return c.Uid
}

func (c Commit) GetURL() string {
return c.Url
}

func (c Commit) GetComitAuthor() CommitAuthor {
return c.ComitAuthor
}

func (c Commit) GetComitComiter() CommitCommitter {
return c.ComitComiter
}

func (c Commit) GetMessage() string {
return c.Message
}
39 changes: 39 additions & 0 deletions pkg/sbom/commitAuthor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2024 Interlynk.io
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sbom

type GetCommitAuthor interface {
GetName() string
GetEmail() string
GetTimestamp() string
}

type CommitAuthor struct {
Name string `json:"name"`
Email string `json:"email"`
Timestamp string `json:"date"`
}

func (cc CommitAuthor) GetName() string {
return cc.Name
}

func (cc CommitAuthor) GetEmail() string {
return cc.Email
}

func (cc CommitAuthor) GetTimestamp() string {
return cc.Timestamp
}
39 changes: 39 additions & 0 deletions pkg/sbom/commitCommiter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2024 Interlynk.io
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sbom

type GetCommitCommitter interface {
GetName() string
GetEmail() string
GetTimestamp() string
}

type CommitCommitter struct {
Timestamp string `json:"date"`
Name string `json:"name"`
Email string `json:"email"`
}

func (cc CommitCommitter) GetName() string {
return cc.Name
}

func (cc CommitCommitter) GetEmail() string {
return cc.Email
}

func (cc CommitCommitter) GetTimestamp() string {
return cc.Timestamp
}
6 changes: 6 additions & 0 deletions pkg/sbom/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type GetComponent interface {
ExternalReferences() []GetExternalReference
GetComposition(string) string
GetPrimaryCompInfo() GetPrimaryComp
GetPedigrees() GetPedigree
}

type Component struct {
Expand Down Expand Up @@ -86,6 +87,7 @@ type Component struct {
PackageLicenseDeclared string
ExternalRefs []GetExternalReference
composition map[string]string
Pedigrees Pedigree
}

func NewComponent() *Component {
Expand Down Expand Up @@ -207,3 +209,7 @@ func (c Component) ExternalReferences() []GetExternalReference {
func (c Component) GetComposition(componentID string) string {
return c.composition[componentID]
}

func (c Component) GetPedigrees() GetPedigree {
return c.Pedigrees
}
Loading

0 comments on commit e9faa90

Please sign in to comment.