-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: json: Add support for JSON key signature verification
- Loading branch information
Showing
8 changed files
with
474 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"github.com/pkg/errors" | ||
"github.com/spf13/cobra" | ||
"github.com/zsrv/supermicro-product-key/pkg/json" | ||
"os" | ||
"text/tabwriter" | ||
) | ||
|
||
func init() { | ||
rootCmd.AddCommand(jsonCmd) | ||
jsonCmd.AddCommand(jsonVerifyCmd) | ||
jsonCmd.AddCommand(jsonBruteForceCmd) | ||
jsonCmd.AddCommand(jsonListSKUCmd) | ||
} | ||
|
||
var jsonCmd = &cobra.Command{ | ||
Use: "json", | ||
Short: "JSON product key operations", | ||
} | ||
|
||
var jsonVerifyCmd = &cobra.Command{ | ||
Use: "verify MAC_ADDRESS PRODUCT_KEY", | ||
Short: "Verify the signature of a JSON product key", | ||
Args: cobra.ExactArgs(2), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
macAddress := args[0] | ||
productKey := args[1] | ||
|
||
if err := json.VerifyProductKeySignature(productKey, macAddress); err != nil { | ||
return errors.WithMessage(err, "product key verification failed") | ||
} | ||
|
||
fmt.Println("signature verified ok") | ||
return nil | ||
}, | ||
} | ||
|
||
var jsonBruteForceCmd = &cobra.Command{ | ||
Use: "bruteforce PRODUCT_KEY", | ||
Short: "Find the MAC address associated with a JSON product key by brute force", | ||
Args: cobra.ExactArgs(1), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
productKey := args[0] | ||
|
||
fmt.Println("searching for mac address ...") | ||
|
||
mac, err := json.BruteForceMACAddressFromString(productKey) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
fmt.Printf("found match! mac = '%s'\n", mac) | ||
return nil | ||
}, | ||
} | ||
|
||
var jsonListSKUCmd = &cobra.Command{ | ||
Use: "listswid", | ||
Short: "Get a list of software identifiers that can be found in product keys", | ||
Args: cobra.ExactArgs(0), | ||
Run: func(cmd *cobra.Command, args []string) { | ||
w := tabwriter.NewWriter(os.Stdout, 3, 1, 2, ' ', 0) | ||
fmt.Fprintf(w, "License SKU\tID\n") | ||
fmt.Fprintf(w, "-----------\t--\n") | ||
for _, swid := range json.SoftwareIdentifiers.List() { | ||
fmt.Fprintf(w, "%v\t%v\n", swid.SKU, swid.ID) | ||
} | ||
w.Flush() | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package json | ||
|
||
import ( | ||
"encoding/hex" | ||
"github.com/pkg/errors" | ||
"github.com/rs/zerolog/log" | ||
"github.com/zsrv/supermicro-product-key/pkg/oob" | ||
"strings" | ||
) | ||
|
||
// BruteForceMACAddressFromString finds the MAC address associated with the productKey. | ||
func BruteForceMACAddressFromString(productKey string) (string, error) { | ||
license, err := unmarshalLicenseFromJSON(productKey) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return BruteForceMACAddress(license) | ||
} | ||
|
||
// BruteForceMACAddress finds the MAC address associated with the license. | ||
func BruteForceMACAddress(license License) (string, error) { | ||
brute := func(macBlock string, result chan string, done chan bool) { | ||
log.Debug().Msgf("searching mac address block '%s'", macBlock) | ||
|
||
for one := 0; one <= 255; one++ { | ||
hexOne := hex.EncodeToString([]byte{byte(one)}) | ||
for two := 0; two <= 255; two++ { | ||
hexTwo := hex.EncodeToString([]byte{byte(two)}) | ||
for three := 0; three <= 255; three++ { | ||
hexThree := hex.EncodeToString([]byte{byte(three)}) | ||
|
||
mac := strings.ToUpper(macBlock + hexOne + hexTwo + hexThree) | ||
if err := license.VerifySignature(mac); err != nil { | ||
continue | ||
} | ||
|
||
result <- mac | ||
done <- true | ||
} | ||
} | ||
} | ||
|
||
log.Debug().Msgf("finished searching mac address block '%s' with no matches", macBlock) | ||
done <- true | ||
} | ||
|
||
result := make(chan string) | ||
done := make(chan bool) | ||
|
||
for _, macBlock := range oob.SupermicroMACAddressBlocks { | ||
go brute(macBlock, result, done) | ||
} | ||
|
||
for range oob.SupermicroMACAddressBlocks { | ||
select { | ||
case resultMAC := <-result: | ||
return strings.ToLower(resultMAC), nil | ||
case <-done: | ||
continue | ||
} | ||
} | ||
|
||
return "", errors.New("could not find a matching mac address") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package json | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestBruteForceMACAddressFromString(t *testing.T) { | ||
type args struct { | ||
productKey string | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want string | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "valid", | ||
args: args{ | ||
productKey: validOOBProductKey, | ||
}, | ||
want: validOOBProductKeyMACAddress, | ||
wantErr: false, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := BruteForceMACAddressFromString(tt.args.productKey) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("BruteForceMACAddressFromString() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if got != tt.want { | ||
t.Errorf("BruteForceMACAddressFromString() got = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package json | ||
|
||
import ( | ||
"crypto/rsa" | ||
"crypto/x509" | ||
"encoding/json" | ||
"encoding/pem" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
func VerifyProductKeySignature(productKey string, macAddress string) error { | ||
license, err := unmarshalLicenseFromJSON(productKey) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return license.VerifySignature(macAddress) | ||
} | ||
|
||
func unmarshalLicenseFromJSON(productKey string) (License, error) { | ||
var license License | ||
err := json.Unmarshal([]byte(productKey), &license) | ||
if err != nil { | ||
return License{}, errors.WithMessage(err, "could not unmarshal license JSON") | ||
} | ||
|
||
return license, nil | ||
} | ||
|
||
// parseRSAPublicKey returns a rsa.PublicKey parsed from pemData. | ||
func parseRSAPublicKey(pemData []byte) (*rsa.PublicKey, error) { | ||
block, _ := pem.Decode(pemData) | ||
if block == nil || block.Type != "RSA PUBLIC KEY" { | ||
return nil, errors.New("failed to decode PEM block containing public key") | ||
} | ||
|
||
pub, err := x509.ParsePKCS1PublicKey(block.Bytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return pub, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package json | ||
|
||
import "testing" | ||
|
||
var validOOBProductKey = `{"ProductKey":{"Node":{"LicenseID":"1","LicenseName":"SFT-OOB-LIC","CreateDate":"20200921"},"Signature":"OAaLKLy5IEK9WnIdnyA9ew89qTKQrm1eu+Q84CbwjR7XG7JGYccec+3vS3y/kQRRej3DcNVQPWsasX86ROTT+LZFsNY2mIEbQ6+Y/Tmv6+jwYgbQjEN6CjI7ahyKcebN12+3cLvPZyRf3kDqgtcpfuw3Qeg8BbhhyHQk29yNp+NG0XbKn02sHTrskvAGgG0GGlDCT5YmNa0gDSMzsvt/eH9nskb5opQNE3j7MAMXbjpI7xVHRbmB2N5iSu8gQUj0/pmk615ztM/uB54ur3GninJRU74S9Kotz+JunJg4pprGyQW544ggmzklmtr3zCA3GK/d929eZsVk5p8UxXG7wQ=="}}` | ||
var validOOBProductKeyMACAddress = "ac1f6b3ddaec" | ||
|
||
func TestVerifyProductKeySignature(t *testing.T) { | ||
type args struct { | ||
productKey string | ||
macAddress string | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "valid", | ||
args: args{ | ||
productKey: validOOBProductKey, | ||
macAddress: validOOBProductKeyMACAddress, | ||
}, | ||
wantErr: false, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if err := VerifyProductKeySignature(tt.args.productKey, tt.args.macAddress); (err != nil) != tt.wantErr { | ||
t.Errorf("VerifyProductKeySignature() error = %v, wantErr %v", err, tt.wantErr) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package json | ||
|
||
import ( | ||
"crypto" | ||
"crypto/rsa" | ||
"crypto/sha256" | ||
"encoding/base64" | ||
"github.com/zsrv/supermicro-product-key/pkg/oob" | ||
"strings" | ||
) | ||
|
||
// licenseSigningPublicKey is used to validate JSON format licenses | ||
// that have been digitally signed by Supermicro. | ||
var licenseSigningPublicKey = []byte(`-----BEGIN RSA PUBLIC KEY----- | ||
MIIBCAKCAQEAvDb4MxWw/FTi8pscP6S2YAl/3gmVOj/StG0lu3PdCSmdpzmzbOU9 | ||
KBS3t0yPZ0ynUQj/qXOwaVLvBJ+uCE0pGIRWkzBersVUzmXXN8Dza5yOzlLIdsVn | ||
amUrKcRHgC+otRE/gnCxIiioacy9TkA96otbAvztCl1j1W8oCixazpfwZrayy12y | ||
CcOyquZr3prngLCgOWa9e9cLSekKuvYXPKPC0CogLz0ueg+y+gcWGHGwbMtERLGf | ||
WYDrXD1mdlV0EL4A5H4v9bqzn0yHe9dIb7+tdMHiN+qLjMzcVSzwfXu7Abk7rPOz | ||
wBY6gMvfhfaOTj8aU5JFQ3DEBdTPOwgtSwIBAw== | ||
-----END RSA PUBLIC KEY-----`) | ||
|
||
type License struct { | ||
ProductKey ProductKey | ||
} | ||
|
||
type ProductKey struct { | ||
Node Node | ||
Signature string | ||
} | ||
|
||
type Node struct { | ||
LicenseID string | ||
LicenseName string | ||
CreateDate string | ||
} | ||
|
||
// VerifySignature returns nil if the license signature is valid when the given | ||
// macAddress is used as input in the message being hashed and signed, or an error | ||
// if one occurs. | ||
func (l *License) VerifySignature(macAddress string) error { | ||
macAddress, err := oob.NormalizeMACAddress(macAddress) | ||
if err != nil { | ||
return err | ||
} | ||
macAddress = strings.ToUpper(macAddress) | ||
|
||
publicKey, err := parseRSAPublicKey(licenseSigningPublicKey) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
signature, err := base64.StdEncoding.DecodeString(l.ProductKey.Signature) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
message := []byte(macAddress + | ||
l.ProductKey.Node.LicenseID + | ||
l.ProductKey.Node.LicenseName + | ||
l.ProductKey.Node.CreateDate) | ||
hashed := sha256.Sum256(message) | ||
|
||
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed[:], signature) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.