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 }