commit d1cfc3cc700833751c205ff6b16189b0192c6a28 Author: ston1th Date: Fri Jan 13 00:05:57 2017 +0100 initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2bb060c --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (C) 2017 Marius Schellenberger +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * The names of the authors and/or contributors may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ston1th BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..89eaf5d --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# msrcgo - a simple API client and JSON decoder for the Microsoft Security Bulletins + +For examples see: `cmd/main.go` + +Or simply call it: `go run cmd/main.go ` + +To get your API-Key, navigate here and log in with your microsoft account: + +https://portal.msrc.microsoft.com/en-us/developer diff --git a/cmd/curl_examples.sh b/cmd/curl_examples.sh new file mode 100755 index 0000000..d036779 --- /dev/null +++ b/cmd/curl_examples.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +key="" +url="https://api.msrc.microsoft.com" +apiversion="?api-version=2016-08-01" +updates="/Updates" + +#all updates +curl -H "api-key: ${key}" ${url}${updates}${apiversion} + +#updates by id +id=2016-Sep +#id=CVE-1234-5678 +#id=2016 +curl -H "api-key: ${key}" "${url}${updates}('${id}')${apiversion}" + +#by id +cvrfid=2017-Jan +#cvrfid=CVE-1234-5678 +#cvrfid=2016 +cvrf="/cvrf/" + +curl -H "Accept: application/json" -H "api-key: ${key}" "${url}${cvrf}${cvrfid}${apiversion}" diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..677f369 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "os" + + "git.giftfish.de/ston1th/msrcgo" +) + +func main() { + apikey := os.Args[1] + c := msrcgo.NewClient(apikey) + all, err := c.GetAllUpdates() + fmt.Println(all, err) + jan, err := c.GetUpdateID("2017-Jan") + fmt.Println(jan, err) + cvrf, err := c.GetCVRF("2017-Jan") + fmt.Println(cvrf, err) +} diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..70d69c0 --- /dev/null +++ b/doc.go @@ -0,0 +1,3 @@ +// Package msrcgo provides a simple API client and JSON decoder for the Microsoft Security Bulletins + +package msrcgo diff --git a/http.go b/http.go new file mode 100644 index 0000000..1228669 --- /dev/null +++ b/http.go @@ -0,0 +1,92 @@ +package msrcgo + +import ( + "errors" + "io" + "io/ioutil" + "net/http" +) + +const ( + APIVersion = "?api-version=2016-08-01" + UpdatesURI = "/Updates" + CvrfURI = "/cvrf/" + URL = "https://api.msrc.microsoft.com" + + APIKeyHeader = "api-key" + AcceptHeader = "Accept" + AcceptValue = "application/json" + GET = "GET" +) + +// Client represents the API client +type Client struct { + key string + allUpdates *http.Request +} + +// NewClient returns a new Client instance +func NewClient(key string) *Client { + all, _ := http.NewRequest(GET, URL+UpdatesURI+APIVersion, nil) + all.Header.Set(AcceptHeader, AcceptValue) + all.Header.Set(APIKeyHeader, key) + return &Client{key, all} +} + +// GetAllUpdates returns the Update struct (a list of all bulletins) +func (c *Client) GetAllUpdates() (*Update, error) { + return c.getUpdate(c.allUpdates) +} + +// GetUpdateID returns a Update struct of the given id +// Example id: "2016-Sep" +func (c *Client) GetUpdateID(id string) (*Update, error) { + req, _ := http.NewRequest(GET, URL+UpdatesURI+"('"+id+"')"+APIVersion, nil) + req.Header.Set(AcceptHeader, AcceptValue) + req.Header.Set(APIKeyHeader, c.key) + return c.getUpdate(req) +} + +// GetCVRF returns the CVRF struct of the given id +// Example id: "2016-Sep" +func (c *Client) GetCVRF(id string) (*CVRF, error) { + req, _ := http.NewRequest(GET, URL+CvrfURI+id+APIVersion, nil) + req.Header.Set(AcceptHeader, AcceptValue) + req.Header.Set(APIKeyHeader, c.key) + return c.getCVRF(req) +} + +func (c *Client) request(req *http.Request) (body io.ReadCloser, err error) { + resp, err := (&http.Client{}).Do(req) + if err != nil { + return + } + if resp.StatusCode != http.StatusOK { + b, e := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if e == nil { + err = errors.New(string(b)) + } + return + } + body = resp.Body + return +} + +func (c *Client) getUpdate(req *http.Request) (u *Update, err error) { + body, err := c.request(req) + if err != nil { + return + } + defer body.Close() + return DecodeUpdate(body) +} + +func (c *Client) getCVRF(req *http.Request) (u *CVRF, err error) { + body, err := c.request(req) + if err != nil { + return + } + defer body.Close() + return DecodeCVRF(body) +} diff --git a/json.go b/json.go new file mode 100644 index 0000000..bd3c9a6 --- /dev/null +++ b/json.go @@ -0,0 +1,127 @@ +package msrcgo + +import ( + "encoding/json" + "io" + "time" +) + +// DecodeUpdate takes an io.Reader and returns the decoded Update struct +func DecodeUpdate(r io.Reader) (u *Update, err error) { + u = new(Update) + err = json.NewDecoder(r).Decode(u) + return +} + +// Update represents the MSRC Updates +type Update struct { + OdataContext string `json:"@odata.context"` + Value []struct { + ID string `json:"ID"` + Alias string `json:"Alias"` + DocumentTitle string `json:"DocumentTitle"` + Severity interface{} `json:"Severity"` + InitialReleaseDate time.Time `json:"InitialReleaseDate"` + CurrentReleaseDate time.Time `json:"CurrentReleaseDate"` + CvrfURL string `json:"CvrfUrl"` + } `json:"value"` +} + +// DecodeCVRF takes an io.Reader and returns the decoded CVRF struct +func DecodeCVRF(r io.Reader) (cvrf *CVRF, err error) { + cvrf = new(CVRF) + err = json.NewDecoder(r).Decode(cvrf) + return +} + +// CVRF represents a single Security Bulletin +type CVRF struct { + DocumentTitle struct { + Value string `json:"Value"` + } `json:"DocumentTitle"` + DocumentType struct { + Value string `json:"Value"` + } `json:"DocumentType"` + DocumentPublisher struct { + ContactDetails struct { + Value string `json:"Value"` + } `json:"ContactDetails"` + IssuingAuthority struct { + Value string `json:"Value"` + } `json:"IssuingAuthority"` + Type int `json:"Type"` + } `json:"DocumentPublisher"` + DocumentTracking struct { + Identification struct { + ID struct { + Value string `json:"Value"` + } `json:"ID"` + Alias struct { + Value string `json:"Value"` + } `json:"Alias"` + } `json:"Identification"` + Status int `json:"Status"` + Version string `json:"Version"` + RevisionHistory []struct { + Number string `json:"Number"` + Date string `json:"Date"` + Description struct { + Value string `json:"Value"` + } `json:"Description"` + } `json:"RevisionHistory"` + InitialReleaseDate string `json:"InitialReleaseDate"` + CurrentReleaseDate string `json:"CurrentReleaseDate"` + } `json:"DocumentTracking"` + DocumentNotes []struct { + Title string `json:"Title"` + Audience string `json:"Audience"` + Type int `json:"Type"` + Ordinal string `json:"Ordinal"` + Value string `json:"Value"` + } `json:"DocumentNotes"` + ProductTree struct { + FullProductName []struct { + ProductID string `json:"ProductID"` + Value string `json:"Value"` + } `json:"FullProductName"` + } `json:"ProductTree"` + Vulnerability []struct { + Title struct { + Value string `json:"Value"` + } `json:"Title"` + Notes []struct { + Type int `json:"Type"` + Value string `json:"Value"` + } `json:"Notes"` + DiscoveryDateSpecified bool `json:"DiscoveryDateSpecified"` + ReleaseDateSpecified bool `json:"ReleaseDateSpecified"` + CVE string `json:"CVE"` + ProductStatuses []struct { + ProductID []string `json:"ProductID"` + Type int `json:"Type"` + } `json:"ProductStatuses"` + CVSSScoreSets []struct { + BaseScore float64 `json:"BaseScore"` + TemporalScore float64 `json:"TemporalScore"` + Vector string `json:"Vector"` + ProductID []string `json:"ProductID"` + } `json:"CVSSScoreSets"` + Remediations []struct { + Description struct { + Value string `json:"Value"` + } `json:"Description"` + URL string `json:"URL"` + ProductID []string `json:"ProductID"` + Type int `json:"Type"` + DateSpecified bool `json:"DateSpecified"` + AffectedFiles []interface{} `json:"AffectedFiles"` + } `json:"Remediations"` + Acknowledgments []struct { + Name []struct { + Value string `json:"Value"` + } `json:"Name"` + URL []string `json:"URL"` + } `json:"Acknowledgments"` + Ordinal string `json:"Ordinal"` + } `json:"Vulnerability"` +} diff --git a/xml.go b/xml.go new file mode 100644 index 0000000..f9f9fb6 --- /dev/null +++ b/xml.go @@ -0,0 +1,86 @@ +package msrcgo + +//NOTE XML is not yet supported + +type xmlcvrf struct { + DocumentTitle string `xml:"DocumentTitle"` + DocumentType string `xml:"DocumentType"` + DocumentPublisher struct { + Type string `xml:"Type,attr"` + ContactDetails string `xml:"ContactDetails"` + IssuingAuthority string `xml:"IssuingAuthority"` + } `xml:"DocumentPublisher"` + DocumentTracking struct { + Identification struct { + ID string `xml:"ID"` + Alias string `xml"Alias"` + } `xml:"Identification"` + Status string `xml:"Status"` + Version string `xml:"Version"` + RevisionHistory struct { + Revision []struct { + Number int `xml:"Number"` + Date string `xml:"Date"` + Description string `xml:"Description"` + } `xml:"Revision"` + } `xml:"RevisionHistory"` + InitialReleaseDate string `xml:"InitialReleaseDate"` + CurrentReleaseDate string `xml:"CurrentReleaseDate"` + } `xml:"DocumentTracking"` + DocumentNotes struct { + Note []struct { + Title string `xml:"Title,attr"` + Audience string `xml:"Audience,attr"` + Type string `xml:"Type,attr"` + Ordinal string `xml:"Ordinal,attr"` + Note string `xml:",chardata"` + } `xml:"Note"` + } `xml:"DocumentNotes"` + ProductTree struct { + FullProductName []struct { + ProductID string `xml:"ProductID,attr"` + FullProductName string `xml:",chardata"` + } `xml:"FullProductName"` + } `xml:"ProductTree"` + Vulnerabilities []struct { + Ordinal string `xml:"Ordinal,attr"` + Title string `xml:"Title"` + Notes struct { + Note []struct { + Type string `xml:"Type,attr"` + Note string `xml:",chardata"` + } `xml:"Note"` + } `xml:"Notes"` + CVE string `xml:"CVE"` + ProductStatuses struct { + Status []struct { + Type string `xml:"Type,attr"` + ProductID string `xml:"ProductID"` + } `xml:"Status"` + } `xml:"ProductStatuses"` + CVSSScoreSets struct { + ScoreSet []struct { + BaseScore float32 `xml:"BaseScore"` + TemporalScore float32 `xml:"TemporalScore"` + Vector string `xml:"Vector"` + ProductID string `xml:"ProductID"` + } `xml:"ScoreSet"` + } `xml:"CVSSScoreSets"` + Remediations struct { + Remediation []struct { + Type string `xml:"Type,attr"` + Description string `xml:"Description"` + URL string `xml:"URL"` + ProductID string `xml:"ProductID"` + //NOTE not yet used + AffectedFields interface{} `xml:"AffectedFields"` + } `xml:"Remediation"` + } `xml:"Remediations"` + Acknowledgments struct { + Acknowledgment []struct { + Name string `xml:"Name"` + URL string `xml:"URL"` + } `json:Acknowledgment"` + } `xml:"Acknowledgments"` + } `xml:"Vulnerability"` +}