github.com/greenpau/go-authcrunch@v1.1.4/pkg/identity/public_key_test.go (about)

     1  // Copyright 2022 Paul Greenberg greenpau@outlook.com
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package identity
    16  
    17  import (
    18  	"bufio"
    19  	"bytes"
    20  	"crypto/rand"
    21  	"crypto/rsa"
    22  	"crypto/x509"
    23  	"encoding/pem"
    24  	"fmt"
    25  	"os"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/greenpau/go-authcrunch/internal/tests"
    30  	"github.com/greenpau/go-authcrunch/pkg/errors"
    31  	"github.com/greenpau/go-authcrunch/pkg/requests"
    32  	"golang.org/x/crypto/ssh"
    33  )
    34  
    35  func readPEMFile(fp string) string {
    36  	var buffer bytes.Buffer
    37  	fileHandle, err := os.Open(fp)
    38  	if err != nil {
    39  		panic(err)
    40  	}
    41  	defer fileHandle.Close()
    42  	scanner := bufio.NewScanner(fileHandle)
    43  	for scanner.Scan() {
    44  		line := scanner.Text()
    45  		buffer.WriteString(strings.TrimSpace(line) + "\n")
    46  	}
    47  	if err := scanner.Err(); err != nil {
    48  		panic(err)
    49  	}
    50  	return buffer.String()
    51  }
    52  
    53  func getPublicKey(t *testing.T, pk *rsa.PrivateKey, keyType string) string {
    54  	switch keyType {
    55  	case "openssh":
    56  		// Create OpenSSH formatted string
    57  		pubKeyOpenSSH, err := ssh.NewPublicKey(pk.Public())
    58  		if err != nil {
    59  			t.Fatalf("failed creating openssh key: %v", err)
    60  		}
    61  		authorizedKeyBytes := ssh.MarshalAuthorizedKey(pubKeyOpenSSH)
    62  		return string(authorizedKeyBytes)
    63  	case "rsa":
    64  		switch pubKey := pk.Public().(type) {
    65  		case *rsa.PublicKey:
    66  			pubKeyBytes := x509.MarshalPKCS1PublicKey(pubKey)
    67  			// Create PEM encoded string
    68  			pubKeyEncoded := pem.EncodeToMemory(
    69  				&pem.Block{
    70  					Type:  "RSA PUBLIC KEY",
    71  					Bytes: pubKeyBytes,
    72  				},
    73  			)
    74  			return string(pubKeyEncoded)
    75  		default:
    76  			t.Fatalf("unsupported key type: %s", keyType)
    77  		}
    78  	default:
    79  		t.Fatalf("unsupported key type: %s", keyType)
    80  	}
    81  	return ""
    82  }
    83  
    84  func TestNewPublicKey(t *testing.T) {
    85  	pk, err := rsa.GenerateKey(rand.Reader, 2048)
    86  	if err != nil {
    87  		t.Fatalf("failed generating private key: %v", err)
    88  	}
    89  	if err := pk.Validate(); err != nil {
    90  		t.Fatalf("failed validating private key: %v", err)
    91  	}
    92  	pkb := x509.MarshalPKCS1PrivateKey(pk)
    93  	pkm := pem.EncodeToMemory(
    94  		&pem.Block{
    95  			Type:  "RSA PRIVATE KEY",
    96  			Bytes: pkb,
    97  		},
    98  	)
    99  	// t.Logf("private rsa key:\n%s", string(pkm))
   100  
   101  	testcases := []struct {
   102  		name      string
   103  		req       *requests.Request
   104  		want      map[string]interface{}
   105  		shouldErr bool
   106  		err       error
   107  	}{
   108  		{
   109  			name: "test ssh rsa key",
   110  			req: &requests.Request{
   111  				Key: requests.Key{
   112  					Usage:   "ssh",
   113  					Comment: "jsmith@outlook.com",
   114  					Payload: "rsa",
   115  				},
   116  			},
   117  			want: map[string]interface{}{
   118  				"usage":   "ssh",
   119  				"type":    "ssh-rsa",
   120  				"comment": "jsmith@outlook.com",
   121  			},
   122  		},
   123  		{
   124  			name: "test openssh key",
   125  			req: &requests.Request{
   126  				Key: requests.Key{
   127  					Usage:   "ssh",
   128  					Comment: "jsmith@outlook.com",
   129  					Payload: "openssh",
   130  				},
   131  			},
   132  			want: map[string]interface{}{
   133  				"usage":   "ssh",
   134  				"type":    "ssh-rsa",
   135  				"comment": "jsmith@outlook.com",
   136  			},
   137  		},
   138  		{
   139  			name: "test unsupported public key usage",
   140  			req: &requests.Request{
   141  				Key: requests.Key{
   142  					Usage:   "foobar",
   143  					Payload: "-----BEGIN RSA PUBLIC KEY-----",
   144  				},
   145  			},
   146  			shouldErr: true,
   147  			err:       errors.ErrPublicKeyInvalidUsage.WithArgs("foobar"),
   148  		},
   149  		{
   150  			name: "test empty public key payload",
   151  			req: &requests.Request{
   152  				Key: requests.Key{
   153  					Usage: "ssh",
   154  				},
   155  			},
   156  			shouldErr: true,
   157  			err:       errors.ErrPublicKeyEmptyPayload,
   158  		},
   159  		{
   160  			name: "test public key payload and usage mismatch",
   161  			req: &requests.Request{
   162  				Key: requests.Key{
   163  					Usage:   "gpg",
   164  					Payload: "-----BEGIN RSA PUBLIC KEY-----",
   165  				},
   166  			},
   167  			shouldErr: true,
   168  			err:       errors.ErrPublicKeyUsagePayloadMismatch.WithArgs("gpg"),
   169  		},
   170  		{
   171  			name: "test public key block type error",
   172  			req: &requests.Request{
   173  				Key: requests.Key{
   174  					Usage:   "ssh",
   175  					Payload: "-----BEGIN RSA PUBLIC KEY-----",
   176  				},
   177  			},
   178  			shouldErr: true,
   179  			err:       errors.ErrPublicKeyBlockType.WithArgs(""),
   180  		},
   181  		{
   182  			name: "test public key unexpected block type",
   183  			req: &requests.Request{
   184  				Key: requests.Key{
   185  					Usage:   "ssh",
   186  					Payload: strings.Replace(string(pkm), "PRIVATE", "PUBLIC", 1),
   187  				},
   188  			},
   189  			shouldErr: true,
   190  			err:       errors.ErrPublicKeyBlockType.WithArgs(""),
   191  		},
   192  		{
   193  			name: "test gpg public key without end block",
   194  			req: &requests.Request{
   195  				Key: requests.Key{
   196  					Usage:   "gpg",
   197  					Comment: "jsmith@outlook.com",
   198  					Payload: "-----BEGIN PGP PUBLIC KEY BLOCK-----",
   199  				},
   200  			},
   201  			shouldErr: true,
   202  			err:       errors.ErrPublicKeyParse.WithArgs("END PGP PUBLIC KEY BLOCK not found"),
   203  		},
   204  		{
   205  			name: "test gpg public key",
   206  			req: &requests.Request{
   207  				Key: requests.Key{
   208  					Usage:   "gpg",
   209  					Payload: readPEMFile("../../testdata/gpg/linux_gpg_pub.pem"),
   210  				},
   211  			},
   212  			want: map[string]interface{}{
   213  				"usage":   "gpg",
   214  				"type":    "dsa",
   215  				"comment": "Google, Inc. Linux Package Signing Key <linux-packages-keymaster@google.com>, algo DSA, created 2007-03-08 20:17:10 +0000 UTC",
   216  				"id":      "a040830f7fac5991",
   217  			},
   218  		},
   219  	}
   220  
   221  	for _, tc := range testcases {
   222  		t.Run(tc.name, func(t *testing.T) {
   223  			msgs := []string{fmt.Sprintf("test name: %s", tc.name)}
   224  			if tc.req.Key.Usage == "ssh" {
   225  				msgs = append(msgs, fmt.Sprintf("private rsa key:\n%s", string(pkm)))
   226  			} else {
   227  				msgs = append(msgs, fmt.Sprintf("payload:\n%s", string(tc.req.Key.Payload)))
   228  			}
   229  
   230  			if tc.req.Key.Payload == "rsa" || tc.req.Key.Payload == "openssh" {
   231  				tc.req.Key.Payload = getPublicKey(t, pk, tc.req.Key.Payload)
   232  			}
   233  			// t.Logf("public key:\n%s", tc.req.Key.Payload)
   234  
   235  			key, err := NewPublicKey(tc.req)
   236  			if tests.EvalErrWithLog(t, err, "new public key", tc.shouldErr, tc.err, msgs) {
   237  				return
   238  			}
   239  			// t.Logf("%v", key)
   240  
   241  			got := make(map[string]interface{})
   242  			got["type"] = key.Type
   243  			got["usage"] = key.Usage
   244  			got["comment"] = key.Comment
   245  			if key.Usage == "gpg" {
   246  				got["id"] = key.ID
   247  			}
   248  			tests.EvalObjectsWithLog(t, "eval", tc.want, got, msgs)
   249  
   250  			bundle := NewPublicKeyBundle()
   251  			bundle.Add(key)
   252  			bundle.Get()
   253  			key.Disable()
   254  		})
   255  	}
   256  }