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