|
|
@@ -0,0 +1,97 @@
|
|
|
+// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
+// SPDX-License-Identifier: BSD-3-Clause
|
|
|
+
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "crypto/ecdsa"
|
|
|
+ "crypto/elliptic"
|
|
|
+ "crypto/rand"
|
|
|
+ "crypto/tls"
|
|
|
+ "crypto/x509"
|
|
|
+ "crypto/x509/pkix"
|
|
|
+ "encoding/pem"
|
|
|
+ "math/big"
|
|
|
+ "net"
|
|
|
+ "os"
|
|
|
+ "path/filepath"
|
|
|
+ "testing"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+// Verify that in --certmode=manual mode, we can use a bare IP address
|
|
|
+// as the --hostname and that GetCertificate will return it.
|
|
|
+func TestCertIP(t *testing.T) {
|
|
|
+ dir := t.TempDir()
|
|
|
+ const hostname = "1.2.3.4"
|
|
|
+
|
|
|
+ priv, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
|
+ serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ ip := net.ParseIP(hostname)
|
|
|
+ if ip == nil {
|
|
|
+ t.Fatalf("invalid IP address %q", hostname)
|
|
|
+ }
|
|
|
+ template := &x509.Certificate{
|
|
|
+ SerialNumber: serialNumber,
|
|
|
+ Subject: pkix.Name{
|
|
|
+ Organization: []string{"Tailscale Test Corp"},
|
|
|
+ },
|
|
|
+ NotBefore: time.Now(),
|
|
|
+ NotAfter: time.Now().Add(30 * 24 * time.Hour),
|
|
|
+
|
|
|
+ KeyUsage: x509.KeyUsageDigitalSignature,
|
|
|
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
|
+ BasicConstraintsValid: true,
|
|
|
+ IPAddresses: []net.IP{ip},
|
|
|
+ }
|
|
|
+ derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ certOut, err := os.Create(filepath.Join(dir, hostname+".crt"))
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
|
|
+ t.Fatalf("Failed to write data to cert.pem: %v", err)
|
|
|
+ }
|
|
|
+ if err := certOut.Close(); err != nil {
|
|
|
+ t.Fatalf("Error closing cert.pem: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ keyOut, err := os.OpenFile(filepath.Join(dir, hostname+".key"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("Unable to marshal private key: %v", err)
|
|
|
+ }
|
|
|
+ if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
|
|
|
+ t.Fatalf("Failed to write data to key.pem: %v", err)
|
|
|
+ }
|
|
|
+ if err := keyOut.Close(); err != nil {
|
|
|
+ t.Fatalf("Error closing key.pem: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ cp, err := certProviderByCertMode("manual", dir, hostname)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ back, err := cp.TLSConfig().GetCertificate(&tls.ClientHelloInfo{
|
|
|
+ ServerName: "", // no SNI
|
|
|
+ })
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("GetCertificate: %v", err)
|
|
|
+ }
|
|
|
+ if back == nil {
|
|
|
+ t.Fatalf("GetCertificate returned nil")
|
|
|
+ }
|
|
|
+}
|