Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
627cb894b6 | ||
|
|
dca4914afe | ||
|
|
1336e21bd6 | ||
|
|
2beaa59a3c | ||
|
|
66c3d8ce71 | ||
|
|
ddb4e80c6a | ||
|
|
f4d47b0db5 | ||
|
|
96dc232fbd | ||
|
|
8c9ba31836 | ||
|
|
559e062ce8 | ||
|
|
5307a0ce6d | ||
|
|
9d779377cf | ||
|
|
8f580defa0 | ||
|
|
a4d1237429 | ||
|
|
b8d8dae13d | ||
|
|
f5a2eabcab | ||
|
|
44ecf3af99 | ||
|
|
ef5341b706 | ||
|
|
b999374650 | ||
|
|
c247dead11 | ||
|
|
1fda73153f | ||
|
|
1b4c3d2e8c | ||
|
|
b903b535d3 | ||
|
|
459a9db11b | ||
|
|
74e709ad8a | ||
|
|
b3790b8d91 | ||
|
|
1dc4269656 | ||
|
|
97bf787255 | ||
|
|
952517d181 | ||
|
|
c6fce02826 | ||
|
|
0ae49b8145 |
+24
-11
@@ -35,6 +35,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -589,8 +590,9 @@ func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (tls.Cer
|
||||
|
||||
// TLSALPN01ChallengeCert creates a certificate for TLS-ALPN-01 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name. For more details on TLS-ALPN-01 see
|
||||
// https://tools.ietf.org/html/draft-shoemaker-acme-tls-alpn-00#section-3
|
||||
// over an identifier (either a DNS name or the textual form of an IPv4 or IPv6
|
||||
// address). For more details on TLS-ALPN-01 see
|
||||
// https://www.rfc-editor.org/rfc/rfc8737 and https://www.rfc-editor.org/rfc/rfc8738
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// If a WithKey option is provided, its private part signs the returned cert,
|
||||
@@ -598,9 +600,13 @@ func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (tls.Cer
|
||||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name in the TLS ClientHello matches the domain, and the special acme-tls/1 ALPN protocol
|
||||
// the server name in the TLS ClientHello matches the identifier, and the special acme-tls/1 ALPN protocol
|
||||
// has been specified.
|
||||
func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption) (cert tls.Certificate, err error) {
|
||||
//
|
||||
// Validation requests for IP address identifiers will use the reverse DNS form in the server name
|
||||
// in the TLS ClientHello since the SNI extension is not supported for IP addresses.
|
||||
// See RFC 8738 Section 6 for more information.
|
||||
func (c *Client) TLSALPN01ChallengeCert(token, identifier string, opt ...CertOption) (cert tls.Certificate, err error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
@@ -630,7 +636,7 @@ func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption)
|
||||
}
|
||||
tmpl.ExtraExtensions = append(tmpl.ExtraExtensions, acmeExtension)
|
||||
newOpt = append(newOpt, WithTemplate(tmpl))
|
||||
return tlsChallengeCert([]string{domain}, newOpt)
|
||||
return tlsChallengeCert(identifier, newOpt)
|
||||
}
|
||||
|
||||
// popNonce returns a nonce value previously stored with c.addNonce
|
||||
@@ -749,10 +755,14 @@ func defaultTLSChallengeCertTemplate() *x509.Certificate {
|
||||
}
|
||||
|
||||
// tlsChallengeCert creates a temporary certificate for TLS-ALPN challenges
|
||||
// with the given SANs and auto-generated public/private key pair.
|
||||
// The Subject Common Name is set to the first SAN to aid debugging.
|
||||
// for the given identifier, using an auto-generated public/private key pair.
|
||||
//
|
||||
// If the provided identifier is a domain name, it will be used as a DNS type SAN and for the
|
||||
// subject common name. If the provided identifier is an IP address it will be used as an IP type
|
||||
// SAN.
|
||||
//
|
||||
// To create a cert with a custom key pair, specify WithKey option.
|
||||
func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||
func tlsChallengeCert(identifier string, opt []CertOption) (tls.Certificate, error) {
|
||||
var key crypto.Signer
|
||||
tmpl := defaultTLSChallengeCertTemplate()
|
||||
for _, o := range opt {
|
||||
@@ -776,9 +786,12 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
}
|
||||
tmpl.DNSNames = san
|
||||
if len(san) > 0 {
|
||||
tmpl.Subject.CommonName = san[0]
|
||||
|
||||
if ip := net.ParseIP(identifier); ip != nil {
|
||||
tmpl.IPAddresses = []net.IP{ip}
|
||||
} else {
|
||||
tmpl.DNSNames = []string{identifier}
|
||||
tmpl.Subject.CommonName = identifier
|
||||
}
|
||||
|
||||
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
||||
|
||||
@@ -561,9 +561,9 @@ func TestHTTPHandlerDefaultFallback(t *testing.T) {
|
||||
{"GET", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"},
|
||||
{"GET", "http://example.org:80/foo?a=b", 302, "https://example.org:443/foo?a=b"},
|
||||
{"GET", "http://example.org:80/foo%20bar", 302, "https://example.org:443/foo%20bar"},
|
||||
{"GET", "http://[2602:d1:xxxx::c60a]:1234", 302, "https://[2602:d1:xxxx::c60a]:443/"},
|
||||
{"GET", "http://[2602:d1:xxxx::c60a]", 302, "https://[2602:d1:xxxx::c60a]/"},
|
||||
{"GET", "http://[2602:d1:xxxx::c60a]/foo?a=b", 302, "https://[2602:d1:xxxx::c60a]/foo?a=b"},
|
||||
{"GET", "http://[2602:d1:abcd::c60a]:1234", 302, "https://[2602:d1:abcd::c60a]:443/"},
|
||||
{"GET", "http://[2602:d1:abcd::c60a]", 302, "https://[2602:d1:abcd::c60a]/"},
|
||||
{"GET", "http://[2602:d1:abcd::c60a]/foo?a=b", 302, "https://[2602:d1:abcd::c60a]/foo?a=b"},
|
||||
{"HEAD", "http://example.org", 302, "https://example.org/"},
|
||||
{"HEAD", "http://example.org/foo", 302, "https://example.org/foo"},
|
||||
{"HEAD", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"},
|
||||
|
||||
@@ -0,0 +1,809 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package acme_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
)
|
||||
|
||||
const (
|
||||
// pebbleModVersion is the module version used for Pebble and Pebble's
|
||||
// challenge test server. It is ignored if `-pebble-local-dir` is provided.
|
||||
pebbleModVersion = "v2.7.0"
|
||||
// startingPort is the first port number used for binding interface
|
||||
// addresses. Each call to takeNextPort() will increment a port number
|
||||
// starting at this value.
|
||||
startingPort = 5555
|
||||
)
|
||||
|
||||
var (
|
||||
pebbleLocalDir = flag.String(
|
||||
"pebble-local-dir",
|
||||
"",
|
||||
"Local Pebble to use, instead of fetching from source",
|
||||
)
|
||||
nextPort atomic.Uint32
|
||||
)
|
||||
|
||||
func init() {
|
||||
nextPort.Store(startingPort)
|
||||
}
|
||||
|
||||
func TestWithPebble(t *testing.T) {
|
||||
// We want to use process groups w/ syscall.Kill, and the acme package
|
||||
// is very platform-agnostic, so skip on non-Linux.
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skip("skipping pebble tests on non-linux OS")
|
||||
}
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("skipping pebble tests in short mode")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
challSrv func(*environment) (challengeServer, string)
|
||||
}{
|
||||
{
|
||||
name: "TLSALPN01-Issuance",
|
||||
challSrv: func(env *environment) (challengeServer, string) {
|
||||
bindAddr := fmt.Sprintf(":%d", env.config.TLSPort)
|
||||
return newChallTLSServer(bindAddr), bindAddr
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "HTTP01-Issuance",
|
||||
challSrv: func(env *environment) (challengeServer, string) {
|
||||
bindAddr := fmt.Sprintf(":%d", env.config.HTTPPort)
|
||||
return newChallHTTPServer(bindAddr), bindAddr
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
env := startPebbleEnvironment(t, nil)
|
||||
challSrv, challSrvAddr := tt.challSrv(&env)
|
||||
challSrv.Run()
|
||||
|
||||
t.Cleanup(func() {
|
||||
challSrv.Shutdown()
|
||||
})
|
||||
|
||||
waitForServer(t, challSrvAddr)
|
||||
testIssuance(t, &env, challSrv)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// challengeServer abstracts over the details of running a challenge response
|
||||
// server for some supported acme.Challenge type. Responses are provisioned
|
||||
// during the test issuance process to be presented to the ACME server's
|
||||
// validation authority.
|
||||
type challengeServer interface {
|
||||
Run()
|
||||
Shutdown() error
|
||||
Supported(chal *acme.Challenge) bool
|
||||
Provision(client *acme.Client, ident acme.AuthzID, chal *acme.Challenge) error
|
||||
}
|
||||
|
||||
// challTLSServer is a simple challenge response server that listens for TLS
|
||||
// connections on a specific port and if they are TLS-ALPN-01 challenge
|
||||
// requests, completes the handshake using the configured challenge response
|
||||
// certificate for the SNI value provided.
|
||||
type challTLSServer struct {
|
||||
*http.Server
|
||||
// mu protects challCerts.
|
||||
mu sync.RWMutex
|
||||
// challCerts is a map from SNI domain name to challenge response certificate.
|
||||
challCerts map[string]*tls.Certificate
|
||||
}
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc8737#section-4
|
||||
const acmeTLSAlpnProtocol = "acme-tls/1"
|
||||
|
||||
func newChallTLSServer(address string) *challTLSServer {
|
||||
challServer := &challTLSServer{Server: &http.Server{
|
||||
Addr: address,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
}, challCerts: make(map[string]*tls.Certificate)}
|
||||
|
||||
// Configure the server to support the TLS-ALPN-01 challenge protocol
|
||||
// and to use a callback for selecting the handshake certificate.
|
||||
challServer.Server.TLSConfig = &tls.Config{
|
||||
NextProtos: []string{acmeTLSAlpnProtocol},
|
||||
GetCertificate: challServer.getCertificate,
|
||||
}
|
||||
|
||||
return challServer
|
||||
}
|
||||
|
||||
func (c *challTLSServer) Shutdown() error {
|
||||
log.Printf("challTLSServer: shutting down")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10)
|
||||
defer cancel()
|
||||
return c.Server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func (c *challTLSServer) Run() {
|
||||
go func() {
|
||||
// Note: certFile and keyFile are empty because our config uses a
|
||||
// GetCertificate callback.
|
||||
if err := c.Server.ListenAndServeTLS("", ""); err != nil {
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Printf("challTLSServer error: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *challTLSServer) Supported(chal *acme.Challenge) bool {
|
||||
return chal.Type == "tls-alpn-01"
|
||||
}
|
||||
|
||||
func (c *challTLSServer) Provision(client *acme.Client, ident acme.AuthzID, chal *acme.Challenge) error {
|
||||
respCert, err := client.TLSALPN01ChallengeCert(chal.Token, ident.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("challTLSServer: failed to generate challlenge response cert for %s: %w",
|
||||
ident.Value, err)
|
||||
}
|
||||
|
||||
log.Printf("challTLSServer: setting challenge response certificate for %s", ident.Value)
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.challCerts[ident.Value] = &respCert
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *challTLSServer) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
// Verify the request looks like a TLS-ALPN-01 challenge request.
|
||||
if len(clientHello.SupportedProtos) != 1 || clientHello.SupportedProtos[0] != acmeTLSAlpnProtocol {
|
||||
return nil, fmt.Errorf(
|
||||
"challTLSServer: non-TLS-ALPN-01 challenge request received with SupportedProtos: %s",
|
||||
clientHello.SupportedProtos)
|
||||
}
|
||||
|
||||
serverName := clientHello.ServerName
|
||||
|
||||
// TLS-ALPN-01 challenge requests for IP addresses are encoded in the SNI
|
||||
// using the reverse-DNS notation. See RFC 8738 Section 6:
|
||||
// https://www.rfc-editor.org/rfc/rfc8738.html#section-6
|
||||
if strings.HasSuffix(serverName, ".in-addr.arpa") {
|
||||
serverName = strings.TrimSuffix(serverName, ".in-addr.arpa")
|
||||
parts := strings.Split(serverName, ".")
|
||||
for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
|
||||
parts[i], parts[j] = parts[j], parts[i]
|
||||
}
|
||||
serverName = strings.Join(parts, ".")
|
||||
}
|
||||
|
||||
log.Printf("challTLSServer: selecting certificate for request from %s for %s",
|
||||
clientHello.Conn.RemoteAddr(), serverName)
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
cert := c.challCerts[serverName]
|
||||
if cert == nil {
|
||||
return nil, fmt.Errorf("challTLSServer: no challenge response certificate configured for %s", serverName)
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// challHTTPServer is a simple challenge response server that listens for HTTP
|
||||
// connections on a specific port and if they are HTTP-01 challenge requests,
|
||||
// serves the challenge response key authorization.
|
||||
type challHTTPServer struct {
|
||||
*http.Server
|
||||
// mu protects challMap
|
||||
mu sync.RWMutex
|
||||
// challMap is a mapping from request path to response body.
|
||||
challMap map[string]string
|
||||
}
|
||||
|
||||
func newChallHTTPServer(address string) *challHTTPServer {
|
||||
challServer := &challHTTPServer{
|
||||
Server: &http.Server{
|
||||
Addr: address,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
},
|
||||
challMap: make(map[string]string),
|
||||
}
|
||||
|
||||
challServer.Server.Handler = challServer
|
||||
|
||||
return challServer
|
||||
}
|
||||
|
||||
func (c *challHTTPServer) Supported(chal *acme.Challenge) bool {
|
||||
return chal.Type == "http-01"
|
||||
}
|
||||
|
||||
func (c *challHTTPServer) Provision(client *acme.Client, ident acme.AuthzID, chall *acme.Challenge) error {
|
||||
path := client.HTTP01ChallengePath(chall.Token)
|
||||
body, err := client.HTTP01ChallengeResponse(chall.Token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate HTTP-01 challenge response for %v challenge %s token %s: %w",
|
||||
ident, chall.URI, chall.Token, err)
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
log.Printf("challHTTPServer: setting challenge response for %s", path)
|
||||
c.challMap[path] = body
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *challHTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("challHTTPServer: handling %s to %s from %s", r.Method, r.URL.Path, r.RemoteAddr)
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
response, exists := c.challMap[r.URL.Path]
|
||||
|
||||
if !exists {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte(response))
|
||||
}
|
||||
|
||||
func (c *challHTTPServer) Shutdown() error {
|
||||
log.Printf("challHTTPServer: shutting down")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
return c.Server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func (c *challHTTPServer) Run() {
|
||||
go func() {
|
||||
if err := c.Server.ListenAndServe(); err != nil {
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Printf("challHTTPServer error: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func testIssuance(t *testing.T, env *environment, challSrv challengeServer) {
|
||||
t.Helper()
|
||||
|
||||
// Bound the total issuance process by a timeout of 60 seconds.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Create a new ACME account.
|
||||
client := env.client
|
||||
acct, err := client.Register(ctx, &acme.Account{}, acme.AcceptTOS)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to register account: %v", err)
|
||||
}
|
||||
if acct.Status != acme.StatusValid {
|
||||
t.Fatalf("expected new account status to be valid, got %v", acct.Status)
|
||||
}
|
||||
log.Printf("registered account: %s", acct.URI)
|
||||
|
||||
// Create a new order for some example identifiers
|
||||
identifiers := []acme.AuthzID{
|
||||
{
|
||||
Type: "dns",
|
||||
Value: "example.com",
|
||||
},
|
||||
{
|
||||
Type: "dns",
|
||||
Value: "www.example.com",
|
||||
},
|
||||
{
|
||||
Type: "ip",
|
||||
Value: "127.0.0.1",
|
||||
},
|
||||
}
|
||||
order, err := client.AuthorizeOrder(ctx, identifiers)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create order for %v: %v", identifiers, err)
|
||||
}
|
||||
if order.Status != acme.StatusPending {
|
||||
t.Fatalf("expected new order status to be pending, got %v", order.Status)
|
||||
}
|
||||
orderURL := order.URI
|
||||
log.Printf("created order: %v", orderURL)
|
||||
|
||||
// For each pending authz provision a supported challenge type's response
|
||||
// with the test challenge server, and tell the ACME server to verify it.
|
||||
for _, authzURL := range order.AuthzURLs {
|
||||
authz, err := client.GetAuthorization(ctx, authzURL)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get order %s authorization %s: %v",
|
||||
orderURL, authzURL, err)
|
||||
}
|
||||
|
||||
if authz.Status != acme.StatusPending {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, challenge := range authz.Challenges {
|
||||
if challenge.Status != acme.StatusPending || !challSrv.Supported(challenge) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := challSrv.Provision(client, authz.Identifier, challenge); err != nil {
|
||||
t.Fatalf("failed to provision challenge %s: %v", challenge.URI, err)
|
||||
}
|
||||
|
||||
_, err = client.Accept(ctx, challenge)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to accept order %s challenge %s: %v",
|
||||
orderURL, challenge.URI, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the order to become ready for finalization.
|
||||
order, err = client.WaitOrder(ctx, order.URI)
|
||||
if err != nil {
|
||||
var orderErr *acme.OrderError
|
||||
if errors.Is(err, orderErr) {
|
||||
t.Fatalf("failed to wait for order %s: %s: %s", orderURL, err, orderErr.Problem)
|
||||
} else {
|
||||
t.Fatalf("failed to wait for order %s: %s", orderURL, err)
|
||||
}
|
||||
}
|
||||
if order.Status != acme.StatusReady {
|
||||
t.Fatalf("expected order %s status to be ready, got %v",
|
||||
orderURL, order.Status)
|
||||
}
|
||||
|
||||
// Generate a certificate keypair and a CSR for the order identifiers.
|
||||
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate certificate key: %v", err)
|
||||
}
|
||||
var dnsNames []string
|
||||
var ipAddresses []net.IP
|
||||
for _, ident := range identifiers {
|
||||
switch ident.Type {
|
||||
case "dns":
|
||||
dnsNames = append(dnsNames, ident.Value)
|
||||
case "ip":
|
||||
ipAddresses = append(ipAddresses, net.ParseIP(ident.Value))
|
||||
default:
|
||||
t.Fatalf("unsupported identifier type: %s", ident.Type)
|
||||
}
|
||||
}
|
||||
csrDer, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{
|
||||
DNSNames: dnsNames,
|
||||
IPAddresses: ipAddresses,
|
||||
}, certKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create CSR: %v", err)
|
||||
}
|
||||
|
||||
// Finalize the order by creating a certificate with our CSR.
|
||||
chain, _, err := client.CreateOrderCert(ctx, order.FinalizeURL, csrDer, true)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to finalize order %s with finalize URL %s: %v",
|
||||
orderURL, order.FinalizeURL, err)
|
||||
}
|
||||
|
||||
// Split the chain into the leaf and any intermediates.
|
||||
leaf := chain[0]
|
||||
intermediatesDER := chain[1:]
|
||||
leafCert, err := x509.ParseCertificate(leaf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse order %s leaf certificate: %v", orderURL, err)
|
||||
}
|
||||
intermediates := x509.NewCertPool()
|
||||
for i, intermediateDER := range intermediatesDER {
|
||||
intermediate, err := x509.ParseCertificate(intermediateDER)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse intermediate %d: %v", i, err)
|
||||
}
|
||||
intermediates.AddCert(intermediate)
|
||||
}
|
||||
|
||||
// Verify there is a valid path from the leaf certificate to Pebble's
|
||||
// issuing root using the provided intermediate certificates.
|
||||
roots, err := env.RootCert()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get Pebble issuer root certs: %v", err)
|
||||
}
|
||||
paths, err := leafCert.Verify(x509.VerifyOptions{
|
||||
Intermediates: intermediates,
|
||||
Roots: roots,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to verify order %s leaf certificate: %v", orderURL, err)
|
||||
}
|
||||
log.Printf("verified %d path(s) from issued leaf certificate to Pebble root CA", len(paths))
|
||||
|
||||
// Also verify that the leaf cert is valid for each of the DNS names
|
||||
// and IP addresses from our order's identifiers.
|
||||
for _, name := range dnsNames {
|
||||
if err := leafCert.VerifyHostname(name); err != nil {
|
||||
t.Fatalf("failed to verify order %s leaf certificate for order DNS name %s: %v",
|
||||
orderURL, name, err)
|
||||
}
|
||||
}
|
||||
for _, ip := range ipAddresses {
|
||||
if err := leafCert.VerifyHostname(ip.String()); err != nil {
|
||||
t.Fatalf("failed to verify order %s leaf certificate for order IP address %s: %v",
|
||||
orderURL, ip, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type environment struct {
|
||||
config *environmentConfig
|
||||
client *acme.Client
|
||||
}
|
||||
|
||||
// RootCert returns the Pebble CA's primary issuing hierarchy root certificate.
|
||||
// This is generated randomly at each startup and can be used to verify
|
||||
// certificate chains issued by Pebble's ACME interface. Note that this
|
||||
// is separate from the static root certificate used by the Pebble ACME
|
||||
// HTTPS interface.
|
||||
func (e *environment) RootCert() (*x509.CertPool, error) {
|
||||
// NOTE: in the future we may want to consider the alternative chains
|
||||
// returned as Link alternative headers.
|
||||
rootURL := fmt.Sprintf("https://%s/roots/0", e.config.pebbleConfig.ManagementListenAddress)
|
||||
resp, err := e.client.HTTPClient.Get(rootURL)
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to GET Pebble root CA from %s: %v", rootURL, err)
|
||||
}
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
rootPEM, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Pebble root CA PEM: %v", err)
|
||||
}
|
||||
rootDERBlock, _ := pem.Decode(rootPEM)
|
||||
rootCA, err := x509.ParseCertificate(rootDERBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Pebble root CA DER: %v", err)
|
||||
}
|
||||
roots.AddCert(rootCA)
|
||||
|
||||
return roots, nil
|
||||
}
|
||||
|
||||
// environmentConfig describes the Pebble configuration, and configuration
|
||||
// shared between pebble and pebble-challtestsrv.
|
||||
type environmentConfig struct {
|
||||
pebbleConfig
|
||||
dnsPort uint32
|
||||
}
|
||||
|
||||
// defaultConfig returns an environmentConfig populated with default values.
|
||||
// The provided pebbleDir is used to specify certificate/private key paths
|
||||
// for the HTTPS ACME interface.
|
||||
func defaultConfig(pebbleDir string) environmentConfig {
|
||||
return environmentConfig{
|
||||
pebbleConfig: pebbleConfig{
|
||||
ListenAddress: fmt.Sprintf("127.0.0.1:%d", takeNextPort()),
|
||||
ManagementListenAddress: fmt.Sprintf("127.0.0.1:%d", takeNextPort()),
|
||||
HTTPPort: takeNextPort(),
|
||||
TLSPort: takeNextPort(),
|
||||
Certificate: fmt.Sprintf("%s/test/certs/localhost/cert.pem", pebbleDir),
|
||||
PrivateKey: fmt.Sprintf("%s/test/certs/localhost/key.pem", pebbleDir),
|
||||
OCSPResponderURL: "",
|
||||
ExternalAccountBindingRequired: false,
|
||||
ExternalAccountMACKeys: make(map[string]string),
|
||||
DomainBlocklist: []string{"blocked-domain.example"},
|
||||
Profiles: map[string]struct {
|
||||
Description string
|
||||
ValidityPeriod uint64
|
||||
}{
|
||||
"default": {
|
||||
Description: "default profile",
|
||||
ValidityPeriod: 3600,
|
||||
},
|
||||
},
|
||||
RetryAfter: struct {
|
||||
Authz int
|
||||
Order int
|
||||
}{
|
||||
3,
|
||||
5,
|
||||
},
|
||||
},
|
||||
dnsPort: takeNextPort(),
|
||||
}
|
||||
}
|
||||
|
||||
// pebbleConfig matches the JSON structure of the Pebble configuration file.
|
||||
type pebbleConfig struct {
|
||||
ListenAddress string
|
||||
ManagementListenAddress string
|
||||
HTTPPort uint32
|
||||
TLSPort uint32
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
OCSPResponderURL string
|
||||
ExternalAccountBindingRequired bool
|
||||
ExternalAccountMACKeys map[string]string
|
||||
DomainBlocklist []string
|
||||
Profiles map[string]struct {
|
||||
Description string
|
||||
ValidityPeriod uint64
|
||||
}
|
||||
RetryAfter struct {
|
||||
Authz int
|
||||
Order int
|
||||
}
|
||||
}
|
||||
|
||||
func takeNextPort() uint32 {
|
||||
return nextPort.Add(1) - 1
|
||||
}
|
||||
|
||||
// startPebbleEnvironment is a test helper that spawns Pebble and Pebble
|
||||
// challenge test server processes based on the provided environmentConfig. The
|
||||
// processes will be torn down when the test ends.
|
||||
func startPebbleEnvironment(t *testing.T, config *environmentConfig) environment {
|
||||
t.Helper()
|
||||
|
||||
var pebbleDir string
|
||||
if *pebbleLocalDir != "" {
|
||||
pebbleDir = *pebbleLocalDir
|
||||
} else {
|
||||
pebbleDir = fetchModule(t, "github.com/letsencrypt/pebble/v2", pebbleModVersion)
|
||||
}
|
||||
|
||||
binDir := prepareBinaries(t, pebbleDir)
|
||||
|
||||
if config == nil {
|
||||
cfg := defaultConfig(pebbleDir)
|
||||
config = &cfg
|
||||
}
|
||||
|
||||
marshalConfig := struct {
|
||||
Pebble pebbleConfig
|
||||
}{
|
||||
Pebble: config.pebbleConfig,
|
||||
}
|
||||
|
||||
configData, err := json.Marshal(marshalConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal config: %v", err)
|
||||
}
|
||||
|
||||
configFile, err := os.CreateTemp("", "pebble-config-*.json")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp config file: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { os.Remove(configFile.Name()) })
|
||||
|
||||
if _, err := configFile.Write(configData); err != nil {
|
||||
t.Fatalf("failed to write config file: %v", err)
|
||||
}
|
||||
configFile.Close()
|
||||
|
||||
log.Printf("pebble dir: %s", pebbleDir)
|
||||
log.Printf("config file: %s", configFile.Name())
|
||||
|
||||
// Spawn the Pebble CA server. It answers ACME requests and performs
|
||||
// outbound validations. We configure it to use a mock DNS server that
|
||||
// always answers 127.0.0.1 for all A queries so that validation
|
||||
// requests for any domain name will resolve to our local challenge
|
||||
// server instances.
|
||||
spawnServerProcess(t, binDir, "pebble", "-config", configFile.Name(),
|
||||
"-dnsserver", fmt.Sprintf("127.0.0.1:%d", config.dnsPort),
|
||||
"-strict")
|
||||
|
||||
// Spawn the Pebble challenge test server. We'll use it to mock DNS
|
||||
// responses but disable all the other interfaces. We want to stand
|
||||
// up our own challenge response servers for TLS-ALPN-01,
|
||||
// etc.
|
||||
// Note: we specify -defaultIPv6 "" so that no AAAA records are served.
|
||||
// The LUCI CI runners have issues with IPv6 connectivity on localhost.
|
||||
spawnServerProcess(t, binDir, "pebble-challtestsrv",
|
||||
"-dns01", fmt.Sprintf(":%d", config.dnsPort),
|
||||
"-defaultIPv6", "",
|
||||
"-management", fmt.Sprintf(":%d", takeNextPort()),
|
||||
"-doh", "",
|
||||
"-http01", "",
|
||||
"-tlsalpn01", "",
|
||||
"-https01", "")
|
||||
|
||||
waitForServer(t, config.pebbleConfig.ListenAddress)
|
||||
waitForServer(t, fmt.Sprintf("127.0.0.1:%d", config.dnsPort))
|
||||
|
||||
log.Printf("pebble environment ready")
|
||||
|
||||
// Construct a cert pool that contains the CA certificate used by the ACME
|
||||
// interface's certificate chain. This is separate from the issuing
|
||||
// hierarchy and is used for the ACME client to interact with the ACME
|
||||
// interface without cert verification error.
|
||||
caCertPath := filepath.Join(pebbleDir, "test/certs/pebble.minica.pem")
|
||||
caCert, err := os.ReadFile(caCertPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read CA certificate %s: %v", caCertPath, err)
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
if !caCertPool.AppendCertsFromPEM(caCert) {
|
||||
t.Fatalf("failed to parse CA certificate %s", caCertPath)
|
||||
}
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create an ACME account keypair/client and verify it can discover
|
||||
// the Pebble server's ACME directory without error.
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate account key: %v", err)
|
||||
}
|
||||
client := &acme.Client{
|
||||
Key: key,
|
||||
HTTPClient: httpClient,
|
||||
DirectoryURL: fmt.Sprintf("https://%s/dir", config.ListenAddress),
|
||||
}
|
||||
_, err = client.Discover(context.TODO())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to discover ACME directory: %v", err)
|
||||
}
|
||||
|
||||
return environment{
|
||||
config: config,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func waitForServer(t *testing.T, addr string) {
|
||||
t.Helper()
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
if conn, err := net.Dial("tcp", addr); err == nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Duration(i*100) * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("failed to connect to %q after 20 tries", addr)
|
||||
}
|
||||
|
||||
// fetchModule fetches the module at the given version and returns the directory
|
||||
// containing its source tree. It skips the test if fetching modules is not
|
||||
// possible in this environment.
|
||||
//
|
||||
// Copied from the stdlib cryptotest.FetchModule and adapted to not rely on the
|
||||
// stdlib internal testenv package.
|
||||
func fetchModule(t *testing.T, module, version string) string {
|
||||
// If the default GOMODCACHE doesn't exist, use a temporary directory
|
||||
// instead. (For example, run.bash sets GOPATH=/nonexist-gopath.)
|
||||
out, err := exec.Command("go", "env", "GOMODCACHE").Output()
|
||||
if err != nil {
|
||||
t.Errorf("go env GOMODCACHE: %v\n%s", err, out)
|
||||
if ee, ok := err.(*exec.ExitError); ok {
|
||||
t.Logf("%s", ee.Stderr)
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
modcacheOk := false
|
||||
if gomodcache := string(bytes.TrimSpace(out)); gomodcache != "" {
|
||||
if _, err := os.Stat(gomodcache); err == nil {
|
||||
modcacheOk = true
|
||||
}
|
||||
}
|
||||
if !modcacheOk {
|
||||
t.Setenv("GOMODCACHE", t.TempDir())
|
||||
// Allow t.TempDir() to clean up subdirectories.
|
||||
t.Setenv("GOFLAGS", os.Getenv("GOFLAGS")+" -modcacherw")
|
||||
}
|
||||
|
||||
t.Logf("fetching %s@%s\n", module, version)
|
||||
|
||||
output, err := exec.Command("go", "mod", "download", "-json", module+"@"+version).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to download %s@%s: %s\n%s\n", module, version, err, output)
|
||||
}
|
||||
var j struct {
|
||||
Dir string
|
||||
}
|
||||
if err := json.Unmarshal(output, &j); err != nil {
|
||||
t.Fatalf("failed to parse 'go mod download': %s\n%s\n", err, output)
|
||||
}
|
||||
|
||||
return j.Dir
|
||||
}
|
||||
|
||||
func prepareBinaries(t *testing.T, pebbleDir string) string {
|
||||
t.Helper()
|
||||
|
||||
// We don't want to build in the module cache dir, which might not be
|
||||
// writable or to pollute the user's clone with binaries if pebbleLocalDir
|
||||
//is used.
|
||||
binDir := t.TempDir()
|
||||
|
||||
build := func(cmd string) {
|
||||
log.Printf("building %s", cmd)
|
||||
buildCmd := exec.Command(
|
||||
"go",
|
||||
"build", "-o", filepath.Join(binDir, cmd), "-mod", "mod", "./cmd/"+cmd)
|
||||
buildCmd.Dir = pebbleDir
|
||||
output, err := buildCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build %s: %s\n%s\n", cmd, err, output)
|
||||
}
|
||||
}
|
||||
|
||||
build("pebble")
|
||||
build("pebble-challtestsrv")
|
||||
|
||||
return binDir
|
||||
}
|
||||
|
||||
func spawnServerProcess(t *testing.T, dir string, cmd string, args ...string) {
|
||||
t.Helper()
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
cmdInstance := exec.Command("./"+cmd, args...)
|
||||
cmdInstance.Dir = dir
|
||||
cmdInstance.Stdout = &stdout
|
||||
cmdInstance.Stderr = &stderr
|
||||
|
||||
if err := cmdInstance.Start(); err != nil {
|
||||
t.Fatalf("failed to start %s: %v", cmd, err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
cmdInstance.Process.Kill()
|
||||
cmdInstance.Wait()
|
||||
|
||||
if t.Failed() || testing.Verbose() {
|
||||
t.Logf("=== %s output ===", cmd)
|
||||
if stdout.Len() > 0 {
|
||||
t.Logf("stdout:\n%s", strings.TrimSpace(stdout.String()))
|
||||
}
|
||||
if stderr.Len() > 0 {
|
||||
t.Logf("stderr:\n%s", strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
+2
-2
@@ -272,7 +272,7 @@ func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) {
|
||||
case err != nil:
|
||||
// Skip and retry.
|
||||
case o.Status == StatusInvalid:
|
||||
return nil, &OrderError{OrderURL: o.URI, Status: o.Status}
|
||||
return nil, &OrderError{OrderURL: o.URI, Status: o.Status, Problem: o.Error}
|
||||
case o.Status == StatusReady || o.Status == StatusValid:
|
||||
return o, nil
|
||||
}
|
||||
@@ -369,7 +369,7 @@ func (c *Client) CreateOrderCert(ctx context.Context, url string, csr []byte, bu
|
||||
}
|
||||
// The only acceptable status post finalize and WaitOrder is "valid".
|
||||
if o.Status != StatusValid {
|
||||
return nil, "", &OrderError{OrderURL: o.URI, Status: o.Status}
|
||||
return nil, "", &OrderError{OrderURL: o.URI, Status: o.Status, Problem: o.Error}
|
||||
}
|
||||
crt, err := c.fetchCertRFC(ctx, o.CertURL, bundle)
|
||||
return crt, o.CertURL, err
|
||||
|
||||
+16
-3
@@ -885,11 +885,17 @@ func TestRFC_WaitOrderError(t *testing.T) {
|
||||
s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Location", s.url("/orders/1"))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
s := StatusPending
|
||||
if count > 0 {
|
||||
s = StatusInvalid
|
||||
// https://www.rfc-editor.org/rfc/rfc8555#section-7.3.3
|
||||
errorData := `{
|
||||
"type": "urn:ietf:params:acme:error:userActionRequired",
|
||||
"detail": "Terms of service have changed",
|
||||
"instance": "https://example.com/acme/agreement/?token=W8Ih3PswD-8"
|
||||
}`
|
||||
fmt.Fprintf(w, `{"status": %q, "error": %s}`, StatusInvalid, errorData)
|
||||
} else {
|
||||
fmt.Fprintf(w, `{"status": %q}`, StatusPending)
|
||||
}
|
||||
fmt.Fprintf(w, `{"status": %q}`, s)
|
||||
count++
|
||||
})
|
||||
s.start()
|
||||
@@ -910,6 +916,13 @@ func TestRFC_WaitOrderError(t *testing.T) {
|
||||
if e.Status != StatusInvalid {
|
||||
t.Errorf("e.Status = %q; want %q", e.Status, StatusInvalid)
|
||||
}
|
||||
if e.Problem == nil {
|
||||
t.Errorf("e.Problem = nil")
|
||||
}
|
||||
expectedProbType := "urn:ietf:params:acme:error:userActionRequired"
|
||||
if e.Problem.ProblemType != expectedProbType {
|
||||
t.Errorf("e.Problem.ProblemType = %q; want %q", e.Problem.ProblemType, expectedProbType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRFC_CreateOrderCert(t *testing.T) {
|
||||
|
||||
+5
-2
@@ -154,13 +154,16 @@ func (a *AuthorizationError) Error() string {
|
||||
|
||||
// OrderError is returned from Client's order related methods.
|
||||
// It indicates the order is unusable and the clients should start over with
|
||||
// AuthorizeOrder.
|
||||
// AuthorizeOrder. A Problem description may be provided with details on
|
||||
// what caused the order to become unusable.
|
||||
//
|
||||
// The clients can still fetch the order object from CA using GetOrder
|
||||
// to inspect its state.
|
||||
type OrderError struct {
|
||||
OrderURL string
|
||||
Status string
|
||||
// Problem is the error that occurred while processing the order.
|
||||
Problem *Error
|
||||
}
|
||||
|
||||
func (oe *OrderError) Error() string {
|
||||
@@ -619,7 +622,7 @@ func (*certOptKey) privateCertOpt() {}
|
||||
//
|
||||
// In TLS ChallengeCert methods, the template is also used as parent,
|
||||
// resulting in a self-signed certificate.
|
||||
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
|
||||
// The DNSNames or IPAddresses fields of t are always overwritten for tls-alpn challenge certs.
|
||||
func WithTemplate(t *x509.Certificate) CertOption {
|
||||
return (*certOptTemplate)(t)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
|
||||
// XOF defines the interface to hash functions that
|
||||
// support arbitrary-length output.
|
||||
//
|
||||
// New callers should prefer the standard library [hash.XOF].
|
||||
type XOF interface {
|
||||
// Write absorbs more data into the hash's state. It panics if called
|
||||
// after Read.
|
||||
@@ -47,6 +49,8 @@ const maxOutputLength = (1 << 32) * 64
|
||||
//
|
||||
// A non-nil key turns the hash into a MAC. The key must between
|
||||
// zero and 32 bytes long.
|
||||
//
|
||||
// The result can be safely interface-upgraded to [hash.XOF].
|
||||
func NewXOF(size uint32, key []byte) (XOF, error) {
|
||||
if len(key) > Size {
|
||||
return nil, errKeySize
|
||||
@@ -93,6 +97,10 @@ func (x *xof) Clone() XOF {
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (x *xof) BlockSize() int {
|
||||
return x.d.BlockSize()
|
||||
}
|
||||
|
||||
func (x *xof) Reset() {
|
||||
x.cfg[0] = byte(Size)
|
||||
binary.LittleEndian.PutUint32(x.cfg[4:], uint32(Size)) // leaf length
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package blake2b
|
||||
|
||||
import "hash"
|
||||
|
||||
var _ hash.XOF = (*xof)(nil)
|
||||
@@ -3,11 +3,14 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package curve25519 provides an implementation of the X25519 function, which
|
||||
// performs scalar multiplication on the elliptic curve known as Curve25519.
|
||||
// See RFC 7748.
|
||||
// performs scalar multiplication on the elliptic curve known as Curve25519
|
||||
// according to [RFC 7748].
|
||||
//
|
||||
// This package is a wrapper for the X25519 implementation
|
||||
// in the crypto/ecdh package.
|
||||
// The curve25519 package is a wrapper for the X25519 implementation in the
|
||||
// crypto/ecdh package. It is [frozen] and is not accepting new features.
|
||||
//
|
||||
// [RFC 7748]: https://datatracker.ietf.org/doc/html/rfc7748
|
||||
// [frozen]: https://go.dev/wiki/Frozen
|
||||
package curve25519
|
||||
|
||||
import "crypto/ecdh"
|
||||
@@ -36,7 +39,7 @@ func ScalarBaseMult(dst, scalar *[32]byte) {
|
||||
curve := ecdh.X25519()
|
||||
priv, err := curve.NewPrivateKey(scalar[:])
|
||||
if err != nil {
|
||||
panic("curve25519: internal error: scalarBaseMult was not 32 bytes")
|
||||
panic("curve25519: " + err.Error())
|
||||
}
|
||||
copy(dst[:], priv.PublicKey().Bytes())
|
||||
}
|
||||
|
||||
+7
-4
@@ -2,16 +2,19 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ed25519 implements the Ed25519 signature algorithm. See
|
||||
// https://ed25519.cr.yp.to/.
|
||||
// Package ed25519 implements the Ed25519 signature algorithm.
|
||||
//
|
||||
// These functions are also compatible with the “Ed25519” function defined in
|
||||
// RFC 8032. However, unlike RFC 8032's formulation, this package's private key
|
||||
// [RFC 8032]. However, unlike RFC 8032's formulation, this package's private key
|
||||
// representation includes a public key suffix to make multiple signing
|
||||
// operations with the same key more efficient. This package refers to the RFC
|
||||
// 8032 private key as the “seed”.
|
||||
//
|
||||
// This package is a wrapper around the standard library crypto/ed25519 package.
|
||||
// The ed25519 package is a wrapper for the Ed25519 implementation in the
|
||||
// crypto/ed25519 package. It is [frozen] and is not accepting new features.
|
||||
//
|
||||
// [RFC 8032]: https://datatracker.ietf.org/doc/html/rfc8032
|
||||
// [frozen]: https://go.dev/wiki/Frozen
|
||||
package ed25519
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
module golang.org/x/crypto
|
||||
|
||||
go 1.23.0
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.21.0 // tagx:ignore
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/term v0.32.0
|
||||
golang.org/x/net v0.45.0 // tagx:ignore
|
||||
golang.org/x/sys v0.37.0
|
||||
golang.org/x/term v0.36.0
|
||||
)
|
||||
|
||||
require golang.org/x/text v0.26.0 // indirect
|
||||
require golang.org/x/text v0.30.0 // indirect
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
|
||||
+10
-19
@@ -2,25 +2,16 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package auth authenticates a message using a secret key.
|
||||
|
||||
The Sum function, viewed as a function of the message for a uniform random
|
||||
key, is designed to meet the standard notion of unforgeability. This means
|
||||
that an attacker cannot find authenticators for any messages not authenticated
|
||||
by the sender, even if the attacker has adaptively influenced the messages
|
||||
authenticated by the sender. For a formal definition see, e.g., Section 2.4
|
||||
of Bellare, Kilian, and Rogaway, "The security of the cipher block chaining
|
||||
message authentication code," Journal of Computer and System Sciences 61 (2000),
|
||||
362–399; http://www-cse.ucsd.edu/~mihir/papers/cbc.html.
|
||||
|
||||
auth does not make any promises regarding "strong" unforgeability; perhaps
|
||||
one valid authenticator can be converted into another valid authenticator for
|
||||
the same message. NaCl also does not make any promises regarding "truncated
|
||||
unforgeability."
|
||||
|
||||
This package is interoperable with NaCl: https://nacl.cr.yp.to/auth.html.
|
||||
*/
|
||||
// Package auth authenticates a message using a secret key.
|
||||
//
|
||||
// This package is interoperable with [NaCl].
|
||||
//
|
||||
// The auth package is essentially a wrapper for HMAC-SHA-512 (implemented by
|
||||
// crypto/hmac and crypto/sha512), truncated to 32 bytes. It is [frozen] and is
|
||||
// not accepting new features.
|
||||
//
|
||||
// [NaCl]: https://nacl.cr.yp.to/auth.html
|
||||
// [frozen]: https://go.dev/wiki/Frozen
|
||||
package auth
|
||||
|
||||
import (
|
||||
|
||||
+8
-13
@@ -4,20 +4,15 @@
|
||||
|
||||
// Package sign signs small messages using public-key cryptography.
|
||||
//
|
||||
// Sign uses Ed25519 to sign messages. The length of messages is not hidden.
|
||||
// Messages should be small because:
|
||||
// 1. The whole message needs to be held in memory to be processed.
|
||||
// 2. Using large messages pressures implementations on small machines to process
|
||||
// plaintext without verifying the signature. This is very dangerous, and this API
|
||||
// discourages it, but a protocol that uses excessive message sizes might present
|
||||
// some implementations with no other choice.
|
||||
// 3. Performance may be improved by working with messages that fit into data caches.
|
||||
// Thus large amounts of data should be chunked so that each message is small.
|
||||
// This package is interoperable with [libsodium], as well as [TweetNaCl].
|
||||
//
|
||||
// This package is not interoperable with the current release of NaCl
|
||||
// (https://nacl.cr.yp.to/sign.html), which does not support Ed25519 yet. However,
|
||||
// it is compatible with the NaCl fork libsodium (https://www.libsodium.org), as well
|
||||
// as TweetNaCl (https://tweetnacl.cr.yp.to/).
|
||||
// The sign package is essentially a wrapper for the Ed25519 signature
|
||||
// algorithm (implemented by crypto/ed25519). It is [frozen] and is not accepting
|
||||
// new features.
|
||||
//
|
||||
// [libsodium]: https://libsodium.gitbook.io/doc/public-key_cryptography/public-key_signatures
|
||||
// [TweetNaCl]: https://tweetnacl.cr.yp.to/
|
||||
// [frozen]: https://go.dev/wiki/Frozen
|
||||
package sign
|
||||
|
||||
import (
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
// The version of OTR implemented by this package has been deprecated
|
||||
// (https://bugs.otr.im/lib/libotr/issues/140). An implementation of OTRv3 is
|
||||
// available at https://github.com/coyim/otr3.
|
||||
//
|
||||
// The otr package is [frozen] and is not accepting new features.
|
||||
//
|
||||
// [frozen]: https://go.dev/wiki/Frozen
|
||||
package otr
|
||||
|
||||
import (
|
||||
|
||||
+9
-5
@@ -4,12 +4,16 @@
|
||||
|
||||
// Package pkcs12 implements some of PKCS#12.
|
||||
//
|
||||
// This implementation is distilled from https://tools.ietf.org/html/rfc7292
|
||||
// and referenced documents. It is intended for decoding P12/PFX-stored
|
||||
// certificates and keys for use with the crypto/tls package.
|
||||
// This implementation is distilled from [RFC 7292] and referenced documents.
|
||||
// It is intended for decoding P12/PFX-stored certificates and keys for use
|
||||
// with the crypto/tls package.
|
||||
//
|
||||
// This package is frozen. If it's missing functionality you need, consider
|
||||
// an alternative like software.sslmate.com/src/go-pkcs12.
|
||||
// The pkcs12 package is [frozen] and is not accepting new features.
|
||||
// If it's missing functionality you need, consider an alternative like
|
||||
// software.sslmate.com/src/go-pkcs12.
|
||||
//
|
||||
// [RFC 7292]: https://datatracker.ietf.org/doc/html/rfc7292
|
||||
// [frozen]: https://go.dev/wiki/Frozen
|
||||
package pkcs12
|
||||
|
||||
import (
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package salsa provides low-level access to functions in the Salsa family.
|
||||
//
|
||||
// Deprecated: this package exposes unsafe low-level operations. New applications
|
||||
// should consider using the AEAD construction in golang.org/x/crypto/chacha20poly1305
|
||||
// instead. Existing users should migrate to golang.org/x/crypto/salsa20.
|
||||
package salsa
|
||||
|
||||
import "math/bits"
|
||||
|
||||
+4
-2
@@ -430,8 +430,9 @@ func (c *client) List() ([]*Key, error) {
|
||||
return keys, nil
|
||||
case *failureAgentMsg:
|
||||
return nil, errors.New("agent: failed to list keys")
|
||||
default:
|
||||
return nil, fmt.Errorf("agent: failed to list keys, unexpected message type %T", msg)
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Sign has the agent sign the data using a protocol 2 key as defined
|
||||
@@ -462,8 +463,9 @@ func (c *client) SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFl
|
||||
return &sig, nil
|
||||
case *failureAgentMsg:
|
||||
return nil, errors.New("agent: failed to sign challenge")
|
||||
default:
|
||||
return nil, fmt.Errorf("agent: failed to sign challenge, unexpected message type %T", msg)
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// unmarshal parses an agent message in packet, returning the parsed
|
||||
|
||||
@@ -7,6 +7,7 @@ package agent
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
@@ -347,6 +348,53 @@ func TestServerResponseTooLarge(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidResponses(t *testing.T) {
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
done := make(chan struct{})
|
||||
defer func() { <-done }()
|
||||
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
agent := NewClient(a)
|
||||
go func() {
|
||||
defer close(done)
|
||||
|
||||
resp := []byte{agentSuccess}
|
||||
msg := make([]byte, 4+len(resp))
|
||||
binary.BigEndian.PutUint32(msg[:4], uint32(len(resp)))
|
||||
copy(msg[4:], resp)
|
||||
|
||||
if _, err := b.Write(msg); err != nil {
|
||||
t.Errorf("unexpected error sending agent reply: %v", err)
|
||||
b.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := b.Write(msg); err != nil {
|
||||
t.Errorf("unexpected error sending agent reply: %v", err)
|
||||
b.Close()
|
||||
}
|
||||
}()
|
||||
_, err = agent.List()
|
||||
if err == nil {
|
||||
t.Fatal("error expected")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "failed to list keys, unexpected message type") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
_, err = agent.Sign(testPublicKeys["rsa"], []byte("message"))
|
||||
if err == nil {
|
||||
t.Fatal("error expected")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "failed to sign challenge, unexpected message type") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
agent, _, cleanup := startOpenSSHAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
+17
-9
@@ -233,7 +233,11 @@ func parseCert(in []byte, privAlgo string) (*Certificate, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The Type() function is intended to return only certificate key types, but
|
||||
// we use certKeyAlgoNames anyway for safety, to match [Certificate.Type].
|
||||
if _, ok := certKeyAlgoNames[k.Type()]; ok {
|
||||
return nil, fmt.Errorf("ssh: the signature key type %q is invalid for certificates", k.Type())
|
||||
}
|
||||
c.SignatureKey = k
|
||||
c.Signature, rest, ok = parseSignatureBody(g.Signature)
|
||||
if !ok || len(rest) > 0 {
|
||||
@@ -301,16 +305,13 @@ type CertChecker struct {
|
||||
SupportedCriticalOptions []string
|
||||
|
||||
// IsUserAuthority should return true if the key is recognized as an
|
||||
// authority for the given user certificate. This allows for
|
||||
// certificates to be signed by other certificates. This must be set
|
||||
// if this CertChecker will be checking user certificates.
|
||||
// authority for user certificate. This must be set if this CertChecker
|
||||
// will be checking user certificates.
|
||||
IsUserAuthority func(auth PublicKey) bool
|
||||
|
||||
// IsHostAuthority should report whether the key is recognized as
|
||||
// an authority for this host. This allows for certificates to be
|
||||
// signed by other keys, and for those other keys to only be valid
|
||||
// signers for particular hostnames. This must be set if this
|
||||
// CertChecker will be checking host certificates.
|
||||
// an authority for this host. This must be set if this CertChecker
|
||||
// will be checking host certificates.
|
||||
IsHostAuthority func(auth PublicKey, address string) bool
|
||||
|
||||
// Clock is used for verifying time stamps. If nil, time.Now
|
||||
@@ -447,12 +448,19 @@ func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
||||
// SignCert signs the certificate with an authority, setting the Nonce,
|
||||
// SignatureKey, and Signature fields. If the authority implements the
|
||||
// MultiAlgorithmSigner interface the first algorithm in the list is used. This
|
||||
// is useful if you want to sign with a specific algorithm.
|
||||
// is useful if you want to sign with a specific algorithm. As specified in
|
||||
// [SSH-CERTS], Section 2.1.1, authority can't be a [Certificate].
|
||||
func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
|
||||
c.Nonce = make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand, c.Nonce); err != nil {
|
||||
return err
|
||||
}
|
||||
// The Type() function is intended to return only certificate key types, but
|
||||
// we use certKeyAlgoNames anyway for safety, to match [Certificate.Type].
|
||||
if _, ok := certKeyAlgoNames[authority.PublicKey().Type()]; ok {
|
||||
return fmt.Errorf("ssh: certificates cannot be used as authority (public key type %q)",
|
||||
authority.PublicKey().Type())
|
||||
}
|
||||
c.SignatureKey = authority.PublicKey()
|
||||
|
||||
if v, ok := authority.(MultiAlgorithmSigner); ok {
|
||||
|
||||
+32
-1
@@ -338,7 +338,9 @@ func TestCertTypes(t *testing.T) {
|
||||
CertType: UserCert,
|
||||
Key: priv.PublicKey(),
|
||||
}
|
||||
cert.SignCert(rand.Reader, priv)
|
||||
if err := cert.SignCert(rand.Reader, priv); err != nil {
|
||||
t.Fatalf("error signing certificate: %v", err)
|
||||
}
|
||||
|
||||
certSigner, err := NewCertSigner(cert, priv)
|
||||
if err != nil {
|
||||
@@ -404,3 +406,32 @@ func TestCertSignWithMultiAlgorithmSigner(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertSignWithCertificate(t *testing.T) {
|
||||
cert := &Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
CertType: UserCert,
|
||||
}
|
||||
if err := cert.SignCert(rand.Reader, testSigners["ecdsa"]); err != nil {
|
||||
t.Fatalf("SignCert: %v", err)
|
||||
}
|
||||
signer, err := NewSignerWithAlgorithms(testSigners["rsa"].(AlgorithmSigner), []string{KeyAlgoRSASHA256})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
certSigner, err := NewCertSigner(cert, signer)
|
||||
if err != nil {
|
||||
t.Fatalf("NewCertSigner: %v", err)
|
||||
}
|
||||
|
||||
cert1 := &Certificate{
|
||||
Key: testPublicKeys["ecdsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
CertType: UserCert,
|
||||
}
|
||||
|
||||
if err := cert1.SignCert(rand.Reader, certSigner); err == nil {
|
||||
t.Fatal("successfully signed a certificate using another certificate, it is expected to fail")
|
||||
}
|
||||
}
|
||||
|
||||
+29
-27
@@ -8,6 +8,7 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"crypto/fips140"
|
||||
"crypto/rc4"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"slices"
|
||||
|
||||
"golang.org/x/crypto/chacha20"
|
||||
"golang.org/x/crypto/internal/poly1305"
|
||||
@@ -93,41 +95,41 @@ func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream,
|
||||
}
|
||||
|
||||
// cipherModes documents properties of supported ciphers. Ciphers not included
|
||||
// are not supported and will not be negotiated, even if explicitly requested in
|
||||
// ClientConfig.Crypto.Ciphers.
|
||||
var cipherModes = map[string]*cipherMode{
|
||||
// Ciphers from RFC 4344, which introduced many CTR-based ciphers. Algorithms
|
||||
// are defined in the order specified in the RFC.
|
||||
CipherAES128CTR: {16, aes.BlockSize, streamCipherMode(0, newAESCTR)},
|
||||
CipherAES192CTR: {24, aes.BlockSize, streamCipherMode(0, newAESCTR)},
|
||||
CipherAES256CTR: {32, aes.BlockSize, streamCipherMode(0, newAESCTR)},
|
||||
// are not supported and will not be negotiated, even if explicitly configured.
|
||||
// When FIPS mode is enabled, only FIPS-approved algorithms are included.
|
||||
var cipherModes = map[string]*cipherMode{}
|
||||
|
||||
// Ciphers from RFC 4345, which introduces security-improved arcfour ciphers.
|
||||
// They are defined in the order specified in the RFC.
|
||||
InsecureCipherRC4128: {16, 0, streamCipherMode(1536, newRC4)},
|
||||
InsecureCipherRC4256: {32, 0, streamCipherMode(1536, newRC4)},
|
||||
func init() {
|
||||
cipherModes[CipherAES128CTR] = &cipherMode{16, aes.BlockSize, streamCipherMode(0, newAESCTR)}
|
||||
cipherModes[CipherAES192CTR] = &cipherMode{24, aes.BlockSize, streamCipherMode(0, newAESCTR)}
|
||||
cipherModes[CipherAES256CTR] = &cipherMode{32, aes.BlockSize, streamCipherMode(0, newAESCTR)}
|
||||
// Use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode,
|
||||
// we'll wire it up to NewGCMForSSH in Go 1.26.
|
||||
//
|
||||
// For now it means we'll work with fips140=on but not fips140=only.
|
||||
cipherModes[CipherAES128GCM] = &cipherMode{16, 12, newGCMCipher}
|
||||
cipherModes[CipherAES256GCM] = &cipherMode{32, 12, newGCMCipher}
|
||||
|
||||
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
|
||||
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
|
||||
// RC4) has problems with weak keys, and should be used with caution."
|
||||
// RFC 4345 introduces improved versions of Arcfour.
|
||||
InsecureCipherRC4: {16, 0, streamCipherMode(0, newRC4)},
|
||||
|
||||
// AEAD ciphers
|
||||
CipherAES128GCM: {16, 12, newGCMCipher},
|
||||
CipherAES256GCM: {32, 12, newGCMCipher},
|
||||
CipherChaCha20Poly1305: {64, 0, newChaCha20Cipher},
|
||||
if fips140.Enabled() {
|
||||
defaultCiphers = slices.DeleteFunc(defaultCiphers, func(algo string) bool {
|
||||
_, ok := cipherModes[algo]
|
||||
return !ok
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
cipherModes[CipherChaCha20Poly1305] = &cipherMode{64, 0, newChaCha20Cipher}
|
||||
// Insecure ciphers not included in the default configuration.
|
||||
cipherModes[InsecureCipherRC4128] = &cipherMode{16, 0, streamCipherMode(1536, newRC4)}
|
||||
cipherModes[InsecureCipherRC4256] = &cipherMode{32, 0, streamCipherMode(1536, newRC4)}
|
||||
cipherModes[InsecureCipherRC4] = &cipherMode{16, 0, streamCipherMode(0, newRC4)}
|
||||
// CBC mode is insecure and so is not included in the default config.
|
||||
// (See https://www.ieee-security.org/TC/SP2013/papers/4977a526.pdf). If absolutely
|
||||
// needed, it's possible to specify a custom Config to enable it.
|
||||
// You should expect that an active attacker can recover plaintext if
|
||||
// you do.
|
||||
InsecureCipherAES128CBC: {16, aes.BlockSize, newAESCBCCipher},
|
||||
|
||||
// 3des-cbc is insecure and is not included in the default
|
||||
// config.
|
||||
InsecureCipherTripleDESCBC: {24, des.BlockSize, newTripleDESCBCCipher},
|
||||
cipherModes[InsecureCipherAES128CBC] = &cipherMode{16, aes.BlockSize, newAESCBCCipher}
|
||||
cipherModes[InsecureCipherTripleDESCBC] = &cipherMode{24, des.BlockSize, newTripleDESCBCCipher}
|
||||
}
|
||||
|
||||
// prefixLen is the length of the packet prefix that contains the packet length
|
||||
|
||||
+9
-17
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -83,7 +84,7 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||
// success
|
||||
return nil
|
||||
} else if ok == authFailure {
|
||||
if m := auth.method(); !contains(tried, m) {
|
||||
if m := auth.method(); !slices.Contains(tried, m) {
|
||||
tried = append(tried, m)
|
||||
}
|
||||
}
|
||||
@@ -97,7 +98,7 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||
findNext:
|
||||
for _, a := range config.Auth {
|
||||
candidateMethod := a.method()
|
||||
if contains(tried, candidateMethod) {
|
||||
if slices.Contains(tried, candidateMethod) {
|
||||
continue
|
||||
}
|
||||
for _, meth := range methods {
|
||||
@@ -117,15 +118,6 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", tried)
|
||||
}
|
||||
|
||||
func contains(list []string, e string) bool {
|
||||
for _, s := range list {
|
||||
if s == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// An AuthMethod represents an instance of an RFC 4252 authentication method.
|
||||
type AuthMethod interface {
|
||||
// auth authenticates user over transport t.
|
||||
@@ -255,7 +247,7 @@ func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiA
|
||||
// Fallback to use if there is no "server-sig-algs" extension or a
|
||||
// common algorithm cannot be found. We use the public key format if the
|
||||
// MultiAlgorithmSigner supports it, otherwise we return an error.
|
||||
if !contains(as.Algorithms(), underlyingAlgo(keyFormat)) {
|
||||
if !slices.Contains(as.Algorithms(), underlyingAlgo(keyFormat)) {
|
||||
return "", fmt.Errorf("ssh: no common public key signature algorithm, server only supports %q for key type %q, signer only supports %v",
|
||||
underlyingAlgo(keyFormat), keyFormat, as.Algorithms())
|
||||
}
|
||||
@@ -284,12 +276,12 @@ func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiA
|
||||
// Filter algorithms based on those supported by MultiAlgorithmSigner.
|
||||
var keyAlgos []string
|
||||
for _, algo := range algorithmsForKeyFormat(keyFormat) {
|
||||
if contains(as.Algorithms(), underlyingAlgo(algo)) {
|
||||
if slices.Contains(as.Algorithms(), underlyingAlgo(algo)) {
|
||||
keyAlgos = append(keyAlgos, algo)
|
||||
}
|
||||
}
|
||||
|
||||
algo, err := findCommon("public key signature algorithm", keyAlgos, serverAlgos)
|
||||
algo, err := findCommon("public key signature algorithm", keyAlgos, serverAlgos, true)
|
||||
if err != nil {
|
||||
// If there is no overlap, return the fallback algorithm to support
|
||||
// servers that fail to list all supported algorithms.
|
||||
@@ -334,7 +326,7 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
||||
// the key try to use the obtained algorithm as if "server-sig-algs" had
|
||||
// not been implemented if supported from the algorithm signer.
|
||||
if !ok && idx < origSignersLen && isRSACert(algo) && algo != CertAlgoRSAv01 {
|
||||
if contains(as.Algorithms(), KeyAlgoRSA) {
|
||||
if slices.Contains(as.Algorithms(), KeyAlgoRSA) {
|
||||
// We retry using the compat algorithm after all signers have
|
||||
// been tried normally.
|
||||
signers = append(signers, &multiAlgorithmSigner{
|
||||
@@ -385,7 +377,7 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
||||
// contain the "publickey" method, do not attempt to authenticate with any
|
||||
// other keys. According to RFC 4252 Section 7, the latter can occur when
|
||||
// additional authentication methods are required.
|
||||
if success == authSuccess || !contains(methods, cb.method()) {
|
||||
if success == authSuccess || !slices.Contains(methods, cb.method()) {
|
||||
return success, methods, err
|
||||
}
|
||||
}
|
||||
@@ -434,7 +426,7 @@ func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
|
||||
// servers send the key type instead. OpenSSH allows any algorithm
|
||||
// that matches the public key, so we do the same.
|
||||
// https://github.com/openssh/openssh-portable/blob/86bdd385/sshconnect2.c#L709
|
||||
if !contains(algorithmsForKeyFormat(key.Type()), msg.Algo) {
|
||||
if !slices.Contains(algorithmsForKeyFormat(key.Type()), msg.Algo) {
|
||||
return false, nil
|
||||
}
|
||||
if !bytes.Equal(msg.PubKey, pubKey) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -1214,7 +1215,7 @@ func (cb configurablePublicKeyCallback) auth(session []byte, user string, c pack
|
||||
if err != nil {
|
||||
return authFailure, nil, err
|
||||
}
|
||||
if success == authSuccess || !contains(methods, cb.method()) {
|
||||
if success == authSuccess || !slices.Contains(methods, cb.method()) {
|
||||
return success, methods, err
|
||||
}
|
||||
|
||||
|
||||
+103
-27
@@ -6,6 +6,7 @@ package ssh
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/fips140"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -83,6 +84,7 @@ var (
|
||||
// supportedKexAlgos specifies key-exchange algorithms implemented by this
|
||||
// package in preference order, excluding those with security issues.
|
||||
supportedKexAlgos = []string{
|
||||
KeyExchangeMLKEM768X25519,
|
||||
KeyExchangeCurve25519,
|
||||
KeyExchangeECDHP256,
|
||||
KeyExchangeECDHP384,
|
||||
@@ -94,6 +96,7 @@ var (
|
||||
// defaultKexAlgos specifies the default preference for key-exchange
|
||||
// algorithms in preference order.
|
||||
defaultKexAlgos = []string{
|
||||
KeyExchangeMLKEM768X25519,
|
||||
KeyExchangeCurve25519,
|
||||
KeyExchangeECDHP256,
|
||||
KeyExchangeECDHP384,
|
||||
@@ -254,6 +257,40 @@ type Algorithms struct {
|
||||
PublicKeyAuths []string
|
||||
}
|
||||
|
||||
func init() {
|
||||
if fips140.Enabled() {
|
||||
defaultHostKeyAlgos = slices.DeleteFunc(defaultHostKeyAlgos, func(algo string) bool {
|
||||
_, err := hashFunc(underlyingAlgo(algo))
|
||||
return err != nil
|
||||
})
|
||||
defaultPubKeyAuthAlgos = slices.DeleteFunc(defaultPubKeyAuthAlgos, func(algo string) bool {
|
||||
_, err := hashFunc(underlyingAlgo(algo))
|
||||
return err != nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func hashFunc(format string) (crypto.Hash, error) {
|
||||
switch format {
|
||||
case KeyAlgoRSASHA256, KeyAlgoECDSA256, KeyAlgoSKED25519, KeyAlgoSKECDSA256:
|
||||
return crypto.SHA256, nil
|
||||
case KeyAlgoECDSA384:
|
||||
return crypto.SHA384, nil
|
||||
case KeyAlgoRSASHA512, KeyAlgoECDSA521:
|
||||
return crypto.SHA512, nil
|
||||
case KeyAlgoED25519:
|
||||
// KeyAlgoED25519 doesn't pre-hash.
|
||||
return 0, nil
|
||||
case KeyAlgoRSA, InsecureKeyAlgoDSA:
|
||||
if fips140.Enabled() {
|
||||
return 0, fmt.Errorf("ssh: hash algorithm for format %q not allowed in FIPS 140 mode", format)
|
||||
}
|
||||
return crypto.SHA1, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("ssh: hash algorithm for format %q not mapped", format)
|
||||
}
|
||||
}
|
||||
|
||||
// SupportedAlgorithms returns algorithms currently implemented by this package,
|
||||
// excluding those with security issues, which are returned by
|
||||
// InsecureAlgorithms. The algorithms listed here are in preference order.
|
||||
@@ -281,21 +318,6 @@ func InsecureAlgorithms() Algorithms {
|
||||
|
||||
var supportedCompressions = []string{compressionNone}
|
||||
|
||||
// hashFuncs keeps the mapping of supported signature algorithms to their
|
||||
// respective hashes needed for signing and verification.
|
||||
var hashFuncs = map[string]crypto.Hash{
|
||||
KeyAlgoRSA: crypto.SHA1,
|
||||
KeyAlgoRSASHA256: crypto.SHA256,
|
||||
KeyAlgoRSASHA512: crypto.SHA512,
|
||||
InsecureKeyAlgoDSA: crypto.SHA1,
|
||||
KeyAlgoECDSA256: crypto.SHA256,
|
||||
KeyAlgoECDSA384: crypto.SHA384,
|
||||
KeyAlgoECDSA521: crypto.SHA512,
|
||||
// KeyAlgoED25519 doesn't pre-hash.
|
||||
KeyAlgoSKECDSA256: crypto.SHA256,
|
||||
KeyAlgoSKED25519: crypto.SHA256,
|
||||
}
|
||||
|
||||
// algorithmsForKeyFormat returns the supported signature algorithms for a given
|
||||
// public key format (PublicKey.Type), in order of preference. See RFC 8332,
|
||||
// Section 2. See also the note in sendKexInit on backwards compatibility.
|
||||
@@ -310,11 +332,40 @@ func algorithmsForKeyFormat(keyFormat string) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// keyFormatForAlgorithm returns the key format corresponding to the given
|
||||
// signature algorithm. It returns an empty string if the signature algorithm is
|
||||
// invalid or unsupported.
|
||||
func keyFormatForAlgorithm(sigAlgo string) string {
|
||||
switch sigAlgo {
|
||||
case KeyAlgoRSA, KeyAlgoRSASHA256, KeyAlgoRSASHA512:
|
||||
return KeyAlgoRSA
|
||||
case CertAlgoRSAv01, CertAlgoRSASHA256v01, CertAlgoRSASHA512v01:
|
||||
return CertAlgoRSAv01
|
||||
case KeyAlgoED25519,
|
||||
KeyAlgoSKED25519,
|
||||
KeyAlgoSKECDSA256,
|
||||
KeyAlgoECDSA256,
|
||||
KeyAlgoECDSA384,
|
||||
KeyAlgoECDSA521,
|
||||
InsecureKeyAlgoDSA,
|
||||
InsecureCertAlgoDSAv01,
|
||||
CertAlgoECDSA256v01,
|
||||
CertAlgoECDSA384v01,
|
||||
CertAlgoECDSA521v01,
|
||||
CertAlgoSKECDSA256v01,
|
||||
CertAlgoED25519v01,
|
||||
CertAlgoSKED25519v01:
|
||||
return sigAlgo
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// isRSA returns whether algo is a supported RSA algorithm, including certificate
|
||||
// algorithms.
|
||||
func isRSA(algo string) bool {
|
||||
algos := algorithmsForKeyFormat(KeyAlgoRSA)
|
||||
return contains(algos, underlyingAlgo(algo))
|
||||
return slices.Contains(algos, underlyingAlgo(algo))
|
||||
}
|
||||
|
||||
func isRSACert(algo string) bool {
|
||||
@@ -336,7 +387,7 @@ func parseError(tag uint8) error {
|
||||
return fmt.Errorf("ssh: parse error in message type %d", tag)
|
||||
}
|
||||
|
||||
func findCommon(what string, client []string, server []string) (common string, err error) {
|
||||
func findCommon(what string, client []string, server []string, isClient bool) (string, error) {
|
||||
for _, c := range client {
|
||||
for _, s := range server {
|
||||
if c == s {
|
||||
@@ -344,7 +395,32 @@ func findCommon(what string, client []string, server []string) (common string, e
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("ssh: no common algorithm for %s; client offered: %v, server offered: %v", what, client, server)
|
||||
err := &AlgorithmNegotiationError{
|
||||
What: what,
|
||||
}
|
||||
if isClient {
|
||||
err.SupportedAlgorithms = client
|
||||
err.RequestedAlgorithms = server
|
||||
} else {
|
||||
err.SupportedAlgorithms = server
|
||||
err.RequestedAlgorithms = client
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// AlgorithmNegotiationError defines the error returned if the client and the
|
||||
// server cannot agree on an algorithm for key exchange, host key, cipher, MAC.
|
||||
type AlgorithmNegotiationError struct {
|
||||
What string
|
||||
// RequestedAlgorithms lists the algorithms supported by the peer.
|
||||
RequestedAlgorithms []string
|
||||
// SupportedAlgorithms lists the algorithms supported on our side.
|
||||
SupportedAlgorithms []string
|
||||
}
|
||||
|
||||
func (a *AlgorithmNegotiationError) Error() string {
|
||||
return fmt.Sprintf("ssh: no common algorithm for %s; we offered: %v, peer offered: %v",
|
||||
a.What, a.SupportedAlgorithms, a.RequestedAlgorithms)
|
||||
}
|
||||
|
||||
// DirectionAlgorithms defines the algorithms negotiated in one direction
|
||||
@@ -379,12 +455,12 @@ var aeadCiphers = map[string]bool{
|
||||
func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMsg) (algs *NegotiatedAlgorithms, err error) {
|
||||
result := &NegotiatedAlgorithms{}
|
||||
|
||||
result.KeyExchange, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos)
|
||||
result.KeyExchange, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos, isClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.HostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
|
||||
result.HostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos, isClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -394,36 +470,36 @@ func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMs
|
||||
ctos, stoc = stoc, ctos
|
||||
}
|
||||
|
||||
ctos.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
|
||||
ctos.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer, isClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stoc.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
|
||||
stoc.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient, isClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !aeadCiphers[ctos.Cipher] {
|
||||
ctos.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
|
||||
ctos.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer, isClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !aeadCiphers[stoc.Cipher] {
|
||||
stoc.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
|
||||
stoc.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient, isClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctos.compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
|
||||
ctos.compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer, isClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stoc.compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
|
||||
stoc.compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient, isClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -488,7 +564,7 @@ func (c *Config) SetDefaults() {
|
||||
if kexAlgoMap[k] != nil {
|
||||
// Ignore the KEX if we have no kexAlgoMap definition.
|
||||
kexs = append(kexs, k)
|
||||
if k == KeyExchangeCurve25519 && !contains(c.KeyExchanges, keyExchangeCurve25519LibSSH) {
|
||||
if k == KeyExchangeCurve25519 && !slices.Contains(c.KeyExchanges, keyExchangeCurve25519LibSSH) {
|
||||
kexs = append(kexs, keyExchangeCurve25519LibSSH)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -174,3 +176,21 @@ func TestFindAgreedAlgorithms(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyFormatAlgorithms(t *testing.T) {
|
||||
supportedAlgos := SupportedAlgorithms()
|
||||
insecureAlgos := InsecureAlgorithms()
|
||||
algoritms := append(supportedAlgos.PublicKeyAuths, insecureAlgos.PublicKeyAuths...)
|
||||
algoritms = append(algoritms, slices.Collect(maps.Keys(certKeyAlgoNames))...)
|
||||
|
||||
for _, algo := range algoritms {
|
||||
keyFormat := keyFormatForAlgorithm(algo)
|
||||
if keyFormat == "" {
|
||||
t.Errorf("got empty key format for algorithm %q", algo)
|
||||
}
|
||||
if !slices.Contains(algorithmsForKeyFormat(keyFormat), algo) {
|
||||
t.Errorf("algorithms for key format %q, does not contain %q", keyFormat, algo)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -16,8 +16,19 @@ References:
|
||||
[PROTOCOL]: https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL?rev=HEAD
|
||||
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
|
||||
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
|
||||
[SSH-CERTS]: https://datatracker.ietf.org/doc/html/draft-miller-ssh-cert-01
|
||||
[FIPS 140-3 mode]: https://go.dev/doc/security/fips140
|
||||
|
||||
This package does not fall under the stability promise of the Go language itself,
|
||||
so its API may be changed when pressing needs arise.
|
||||
|
||||
# FIPS 140-3 mode
|
||||
|
||||
When the program is in [FIPS 140-3 mode], this package behaves as if only SP
|
||||
800-140C and SP 800-140D approved cipher suites, signature algorithms,
|
||||
certificate public key types and sizes, and key exchange and derivation
|
||||
algorithms were implemented. Others are silently ignored and not negotiated, or
|
||||
rejected. This set may depend on the algorithms supported by the FIPS 140-3 Go
|
||||
Cryptographic Module selected with GOFIPS140, and may change across Go versions.
|
||||
*/
|
||||
package ssh
|
||||
|
||||
+5
-4
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
@@ -527,7 +528,7 @@ func (t *handshakeTransport) sendKexInit() error {
|
||||
switch s := k.(type) {
|
||||
case MultiAlgorithmSigner:
|
||||
for _, algo := range algorithmsForKeyFormat(keyFormat) {
|
||||
if contains(s.Algorithms(), underlyingAlgo(algo)) {
|
||||
if slices.Contains(s.Algorithms(), underlyingAlgo(algo)) {
|
||||
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algo)
|
||||
}
|
||||
}
|
||||
@@ -679,7 +680,7 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if t.sessionID == nil && ((isClient && contains(serverInit.KexAlgos, kexStrictServer)) || (!isClient && contains(clientInit.KexAlgos, kexStrictClient))) {
|
||||
if t.sessionID == nil && ((isClient && slices.Contains(serverInit.KexAlgos, kexStrictServer)) || (!isClient && slices.Contains(clientInit.KexAlgos, kexStrictClient))) {
|
||||
t.strictMode = true
|
||||
if err := t.conn.setStrictMode(); err != nil {
|
||||
return err
|
||||
@@ -736,7 +737,7 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
||||
// On the server side, after the first SSH_MSG_NEWKEYS, send a SSH_MSG_EXT_INFO
|
||||
// message with the server-sig-algs extension if the client supports it. See
|
||||
// RFC 8308, Sections 2.4 and 3.1, and [PROTOCOL], Section 1.9.
|
||||
if !isClient && firstKeyExchange && contains(clientInit.KexAlgos, "ext-info-c") {
|
||||
if !isClient && firstKeyExchange && slices.Contains(clientInit.KexAlgos, "ext-info-c") {
|
||||
supportedPubKeyAuthAlgosList := strings.Join(t.publicKeyAuthAlgorithms, ",")
|
||||
extInfo := &extInfoMsg{
|
||||
NumExtensions: 2,
|
||||
@@ -790,7 +791,7 @@ func (a algorithmSignerWrapper) SignWithAlgorithm(rand io.Reader, data []byte, a
|
||||
func pickHostKey(hostKeys []Signer, algo string) AlgorithmSigner {
|
||||
for _, k := range hostKeys {
|
||||
if s, ok := k.(MultiAlgorithmSigner); ok {
|
||||
if !contains(s.Algorithms(), underlyingAlgo(algo)) {
|
||||
if !slices.Contains(s.Algorithms(), underlyingAlgo(algo)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1294,3 +1294,73 @@ func TestNegotiatedAlgorithms(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlgorithmNegotiationError(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
Config: Config{
|
||||
Ciphers: []string{CipherAES128CTR, CipherAES256CTR},
|
||||
},
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
return &Permissions{}, nil
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
srvErrCh := make(chan error, 1)
|
||||
go func() {
|
||||
_, _, _, err := NewServerConn(c1, serverConf)
|
||||
srvErrCh <- err
|
||||
}()
|
||||
|
||||
clientConf := &ClientConfig{
|
||||
Config: Config{
|
||||
Ciphers: []string{CipherAES128GCM, CipherAES256GCM},
|
||||
},
|
||||
User: "test",
|
||||
Auth: []AuthMethod{Password("testpw")},
|
||||
HostKeyCallback: FixedHostKey(testSigners["rsa"].PublicKey()),
|
||||
}
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", clientConf)
|
||||
if err == nil {
|
||||
t.Fatal("client connection succeded expected algorithm negotiation error")
|
||||
}
|
||||
var negotiationError *AlgorithmNegotiationError
|
||||
if !errors.As(err, &negotiationError) {
|
||||
t.Fatalf("expected algorithm negotiation error, got %v", err)
|
||||
}
|
||||
expectedErrorString := fmt.Sprintf("ssh: handshake failed: ssh: no common algorithm for client to server cipher; we offered: %v, peer offered: %v",
|
||||
clientConf.Ciphers, serverConf.Ciphers)
|
||||
if err.Error() != expectedErrorString {
|
||||
t.Fatalf("expected error string %q, got %q", expectedErrorString, err.Error())
|
||||
}
|
||||
if !reflect.DeepEqual(negotiationError.RequestedAlgorithms, serverConf.Ciphers) {
|
||||
t.Fatalf("expected requested algorithms %v, got %v", serverConf.Ciphers, negotiationError.RequestedAlgorithms)
|
||||
}
|
||||
if !reflect.DeepEqual(negotiationError.SupportedAlgorithms, clientConf.Ciphers) {
|
||||
t.Fatalf("expected supported algorithms %v, got %v", clientConf.Ciphers, negotiationError.SupportedAlgorithms)
|
||||
}
|
||||
err = <-srvErrCh
|
||||
negotiationError = nil
|
||||
if !errors.As(err, &negotiationError) {
|
||||
t.Fatalf("expected algorithm negotiation error, got %v", err)
|
||||
}
|
||||
expectedErrorString = fmt.Sprintf("ssh: no common algorithm for client to server cipher; we offered: %v, peer offered: %v",
|
||||
serverConf.Ciphers, clientConf.Ciphers)
|
||||
if err.Error() != expectedErrorString {
|
||||
t.Fatalf("expected error string %q, got %q", expectedErrorString, err.Error())
|
||||
}
|
||||
if !reflect.DeepEqual(negotiationError.RequestedAlgorithms, clientConf.Ciphers) {
|
||||
t.Fatalf("expected requested algorithms %v, got %v", clientConf.Ciphers, negotiationError.RequestedAlgorithms)
|
||||
}
|
||||
if !reflect.DeepEqual(negotiationError.SupportedAlgorithms, serverConf.Ciphers) {
|
||||
t.Fatalf("expected supported algorithms %v, got %v", serverConf.Ciphers, negotiationError.SupportedAlgorithms)
|
||||
}
|
||||
}
|
||||
|
||||
+34
-20
@@ -8,13 +8,14 @@ import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/fips140"
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"slices"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
@@ -396,9 +397,27 @@ func ecHash(curve elliptic.Curve) crypto.Hash {
|
||||
return crypto.SHA512
|
||||
}
|
||||
|
||||
// kexAlgoMap defines the supported KEXs. KEXs not included are not supported
|
||||
// and will not be negotiated, even if explicitly configured. When FIPS mode is
|
||||
// enabled, only FIPS-approved algorithms are included.
|
||||
var kexAlgoMap = map[string]kexAlgorithm{}
|
||||
|
||||
func init() {
|
||||
// mlkem768x25519-sha256 we'll work with fips140=on but not fips140=only
|
||||
// until Go 1.26.
|
||||
kexAlgoMap[KeyExchangeMLKEM768X25519] = &mlkem768WithCurve25519sha256{}
|
||||
kexAlgoMap[KeyExchangeECDHP521] = &ecdh{elliptic.P521()}
|
||||
kexAlgoMap[KeyExchangeECDHP384] = &ecdh{elliptic.P384()}
|
||||
kexAlgoMap[KeyExchangeECDHP256] = &ecdh{elliptic.P256()}
|
||||
|
||||
if fips140.Enabled() {
|
||||
defaultKexAlgos = slices.DeleteFunc(defaultKexAlgos, func(algo string) bool {
|
||||
_, ok := kexAlgoMap[algo]
|
||||
return !ok
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
p, _ := new(big.Int).SetString(oakleyGroup2, 16)
|
||||
kexAlgoMap[InsecureKeyExchangeDH1SHA1] = &dhGroup{
|
||||
g: new(big.Int).SetInt64(2),
|
||||
@@ -432,9 +451,6 @@ func init() {
|
||||
hashFunc: crypto.SHA512,
|
||||
}
|
||||
|
||||
kexAlgoMap[KeyExchangeECDHP521] = &ecdh{elliptic.P521()}
|
||||
kexAlgoMap[KeyExchangeECDHP384] = &ecdh{elliptic.P384()}
|
||||
kexAlgoMap[KeyExchangeECDHP256] = &ecdh{elliptic.P256()}
|
||||
kexAlgoMap[KeyExchangeCurve25519] = &curve25519sha256{}
|
||||
kexAlgoMap[keyExchangeCurve25519LibSSH] = &curve25519sha256{}
|
||||
kexAlgoMap[InsecureKeyExchangeDHGEXSHA1] = &dhGEXSHA{hashFunc: crypto.SHA1}
|
||||
@@ -454,15 +470,17 @@ func (kp *curve25519KeyPair) generate(rand io.Reader) error {
|
||||
if _, err := io.ReadFull(rand, kp.priv[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
curve25519.ScalarBaseMult(&kp.pub, &kp.priv)
|
||||
p, err := curve25519.X25519(kp.priv[:], curve25519.Basepoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("curve25519: %w", err)
|
||||
}
|
||||
if len(p) != 32 {
|
||||
return fmt.Errorf("curve25519: internal error: X25519 returned %d bytes, expected 32", len(p))
|
||||
}
|
||||
copy(kp.pub[:], p)
|
||||
return nil
|
||||
}
|
||||
|
||||
// curve25519Zeros is just an array of 32 zero bytes so that we have something
|
||||
// convenient to compare against in order to reject curve25519 points with the
|
||||
// wrong order.
|
||||
var curve25519Zeros [32]byte
|
||||
|
||||
func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
||||
var kp curve25519KeyPair
|
||||
if err := kp.generate(rand); err != nil {
|
||||
@@ -485,11 +503,9 @@ func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handsh
|
||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong length")
|
||||
}
|
||||
|
||||
var servPub, secret [32]byte
|
||||
copy(servPub[:], reply.EphemeralPubKey)
|
||||
curve25519.ScalarMult(&secret, &kp.priv, &servPub)
|
||||
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
|
||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
|
||||
secret, err := curve25519.X25519(kp.priv[:], reply.EphemeralPubKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssh: peer's curve25519 public value is not valid: %w", err)
|
||||
}
|
||||
|
||||
h := crypto.SHA256.New()
|
||||
@@ -531,11 +547,9 @@ func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handsh
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var clientPub, secret [32]byte
|
||||
copy(clientPub[:], kexInit.ClientPubKey)
|
||||
curve25519.ScalarMult(&secret, &kp.priv, &clientPub)
|
||||
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
|
||||
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
|
||||
secret, err := curve25519.X25519(kp.priv[:], kexInit.ClientPubKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssh: peer's curve25519 public value is not valid: %w", err)
|
||||
}
|
||||
|
||||
hostKeyBytes := priv.PublicKey().Marshal()
|
||||
|
||||
+55
-16
@@ -27,6 +27,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh/internal/bcrypt_pbkdf"
|
||||
@@ -89,6 +90,11 @@ func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err err
|
||||
}
|
||||
return cert, nil, nil
|
||||
}
|
||||
if keyFormat := keyFormatForAlgorithm(algo); keyFormat != "" {
|
||||
return nil, nil, fmt.Errorf("ssh: signature algorithm %q isn't a key format; key is malformed and should be re-encoded with type %q",
|
||||
algo, keyFormat)
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", algo)
|
||||
}
|
||||
|
||||
@@ -191,9 +197,10 @@ func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey
|
||||
return "", nil, nil, "", nil, io.EOF
|
||||
}
|
||||
|
||||
// ParseAuthorizedKey parses a public key from an authorized_keys
|
||||
// file used in OpenSSH according to the sshd(8) manual page.
|
||||
// ParseAuthorizedKey parses a public key from an authorized_keys file used in
|
||||
// OpenSSH according to the sshd(8) manual page. Invalid lines are ignored.
|
||||
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) {
|
||||
var lastErr error
|
||||
for len(in) > 0 {
|
||||
end := bytes.IndexByte(in, '\n')
|
||||
if end != -1 {
|
||||
@@ -222,6 +229,8 @@ func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []str
|
||||
|
||||
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
||||
return out, comment, options, rest, nil
|
||||
} else {
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
// No key type recognised. Maybe there's an options field at
|
||||
@@ -264,16 +273,22 @@ func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []str
|
||||
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
||||
options = candidateOptions
|
||||
return out, comment, options, rest, nil
|
||||
} else {
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
in = rest
|
||||
continue
|
||||
}
|
||||
|
||||
if lastErr != nil {
|
||||
return nil, "", nil, nil, fmt.Errorf("ssh: no key found; last parsing error for ignored line: %w", lastErr)
|
||||
}
|
||||
|
||||
return nil, "", nil, nil, errors.New("ssh: no key found")
|
||||
}
|
||||
|
||||
// ParsePublicKey parses an SSH public key formatted for use in
|
||||
// ParsePublicKey parses an SSH public key or certificate formatted for use in
|
||||
// the SSH wire protocol according to RFC 4253, section 6.6.
|
||||
func ParsePublicKey(in []byte) (out PublicKey, err error) {
|
||||
algo, in, ok := parseString(in)
|
||||
@@ -395,11 +410,11 @@ func NewSignerWithAlgorithms(signer AlgorithmSigner, algorithms []string) (Multi
|
||||
}
|
||||
|
||||
for _, algo := range algorithms {
|
||||
if !contains(supportedAlgos, algo) {
|
||||
if !slices.Contains(supportedAlgos, algo) {
|
||||
return nil, fmt.Errorf("ssh: algorithm %q is not supported for key type %q",
|
||||
algo, signer.PublicKey().Type())
|
||||
}
|
||||
if !contains(signerAlgos, algo) {
|
||||
if !slices.Contains(signerAlgos, algo) {
|
||||
return nil, fmt.Errorf("ssh: algorithm %q is restricted for the provided signer", algo)
|
||||
}
|
||||
}
|
||||
@@ -486,10 +501,13 @@ func (r *rsaPublicKey) Marshal() []byte {
|
||||
|
||||
func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||
supportedAlgos := algorithmsForKeyFormat(r.Type())
|
||||
if !contains(supportedAlgos, sig.Format) {
|
||||
if !slices.Contains(supportedAlgos, sig.Format) {
|
||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type())
|
||||
}
|
||||
hash := hashFuncs[sig.Format]
|
||||
hash, err := hashFunc(sig.Format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := hash.New()
|
||||
h.Write(data)
|
||||
digest := h.Sum(nil)
|
||||
@@ -606,7 +624,11 @@ func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||
if sig.Format != k.Type() {
|
||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
||||
}
|
||||
h := hashFuncs[sig.Format].New()
|
||||
hash, err := hashFunc(sig.Format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := hash.New()
|
||||
h.Write(data)
|
||||
digest := h.Sum(nil)
|
||||
|
||||
@@ -651,7 +673,11 @@ func (k *dsaPrivateKey) SignWithAlgorithm(rand io.Reader, data []byte, algorithm
|
||||
return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm)
|
||||
}
|
||||
|
||||
h := hashFuncs[k.PublicKey().Type()].New()
|
||||
hash, err := hashFunc(k.PublicKey().Type())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := hash.New()
|
||||
h.Write(data)
|
||||
digest := h.Sum(nil)
|
||||
r, s, err := dsa.Sign(rand, k.PrivateKey, digest)
|
||||
@@ -801,8 +827,11 @@ func (k *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||
if sig.Format != k.Type() {
|
||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
||||
}
|
||||
|
||||
h := hashFuncs[sig.Format].New()
|
||||
hash, err := hashFunc(sig.Format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := hash.New()
|
||||
h.Write(data)
|
||||
digest := h.Sum(nil)
|
||||
|
||||
@@ -905,8 +934,11 @@ func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error {
|
||||
if sig.Format != k.Type() {
|
||||
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
||||
}
|
||||
|
||||
h := hashFuncs[sig.Format].New()
|
||||
hash, err := hashFunc(sig.Format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := hash.New()
|
||||
h.Write([]byte(k.application))
|
||||
appDigest := h.Sum(nil)
|
||||
|
||||
@@ -1009,7 +1041,11 @@ func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error {
|
||||
return fmt.Errorf("invalid size %d for Ed25519 public key", l)
|
||||
}
|
||||
|
||||
h := hashFuncs[sig.Format].New()
|
||||
hash, err := hashFunc(sig.Format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := hash.New()
|
||||
h.Write([]byte(k.application))
|
||||
appDigest := h.Sum(nil)
|
||||
|
||||
@@ -1112,11 +1148,14 @@ func (s *wrappedSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm
|
||||
algorithm = s.pubKey.Type()
|
||||
}
|
||||
|
||||
if !contains(s.Algorithms(), algorithm) {
|
||||
if !slices.Contains(s.Algorithms(), algorithm) {
|
||||
return nil, fmt.Errorf("ssh: unsupported signature algorithm %q for key format %q", algorithm, s.pubKey.Type())
|
||||
}
|
||||
|
||||
hashFunc := hashFuncs[algorithm]
|
||||
hashFunc, err := hashFunc(algorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var digest []byte
|
||||
if hashFunc != 0 {
|
||||
h := hashFunc.New()
|
||||
|
||||
@@ -59,6 +59,17 @@ func TestKeyMarshalParse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePublicKeyWithSigningAlgoAsKeyFormat(t *testing.T) {
|
||||
key := []byte(`rsa-sha2-256 AAAADHJzYS1zaGEyLTI1NgAAAAMBAAEAAAEBAJ7qMyjLXEJCCJmRknuCLo0uPi5GrPY5pQYr84lhlN8Gor5KVL2LKYCW4e70r5xzj7SrHHSCft1FMlYg1KDO9xrprJh733kQqAPWETmSuH0EfRtGtcH6EarKyVxk6As076/yNiiMKVBtG0RPa1L7FviTfcYK4vnCCVrbv3RmA5CCzuG5BSMbRLxzVb4Ri3p8jhxYT8N4QGe/2yqvJLys5vQ9szpZR3tcFp3DJIVZhBRfR6LnoY23XZniAAMQaUVBX86dXQ++dNwAwZSXSt9Og+AniOCiBYqhNVa5n3DID/H7YtEtG+CbZr3r2KD3fv8AfSLRar4XOp8rsRdD31h/kr8=`)
|
||||
_, _, _, _, err := ParseAuthorizedKey(key)
|
||||
if err == nil {
|
||||
t.Fatal("parsing a public key using a signature algorithm as the key format succeeded unexpectedly")
|
||||
}
|
||||
if !strings.Contains(err.Error(), `signature algorithm "rsa-sha2-256" isn't a key format`) {
|
||||
t.Errorf(`got %v, expected 'signature algorithm "rsa-sha2-256" isn't a key format'`, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsupportedCurves(t *testing.T) {
|
||||
raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
|
||||
if err != nil {
|
||||
@@ -810,3 +821,30 @@ func TestCryptoPublicKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCertWithCertSignatureKey(t *testing.T) {
|
||||
certBytes := []byte(`-----BEGIN SSH CERTIFICATE-----
|
||||
AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIPSp27hvNSB0
|
||||
IotJnVhjC4zxNgNS8BHlUCxD0VJi4D/eAAAAIIJMi1e5qfx+IFuKD/p/Ssqcb3os
|
||||
CpOw/4wBs1pQ53zwAAAAAAAAAAEAAAACAAAAAAAAABMAAAAPZm9vLmV4YW1wbGUu
|
||||
Y29tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0AAAAgc3NoLWVkMjU1
|
||||
MTktY2VydC12MDFAb3BlbnNzaC5jb20AAAAg+sNYhCO35mQT1UBMpmMk8ey+culd
|
||||
IU8vBlPEl4B07swAAAAggiv+RLnboS4znGCVl/n1jDg2uD0h15tW4s/04eS2mLQA
|
||||
AAAAAAAAAQAAAAIAAAAAAAAAEwAAAA9mb28uZXhhbXBsZS5jb20AAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACCV2wETgLKL
|
||||
Kt0bRl3YUnd/ZYSlq0xJMbn4Jj3cdPWykQAAAFMAAAALc3NoLWVkMjU1MTkAAABA
|
||||
WOdbRGEzyRAhiIK227CLUQD5caXYMV8FvSIB7toEE2M/8HnWdG9H3Rsg/v3unruQ
|
||||
JrQldnuPJNe7KOP2+zvUDgAAAFMAAAALc3NoLWVkMjU1MTkAAABAm3bIPp85ZpIe
|
||||
D+izJcUqlcAOri7HO8bULFNHT6LVegvB06xQ5TLwMlrxWUF4cafl1tSe8JQck4a6
|
||||
cLYUOHfQDw==
|
||||
-----END SSH CERTIFICATE-----
|
||||
`)
|
||||
block, _ := pem.Decode(certBytes)
|
||||
if block == nil {
|
||||
t.Fatal("invalid test certificate")
|
||||
}
|
||||
|
||||
if _, err := ParsePublicKey(block.Bytes); err == nil {
|
||||
t.Fatal("parsing an SSH certificate using another certificate as signature key succeeded; expected failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,20 +421,26 @@ func New(files ...string) (ssh.HostKeyCallback, error) {
|
||||
return certChecker.CheckHostKey, nil
|
||||
}
|
||||
|
||||
// Normalize normalizes an address into the form used in known_hosts
|
||||
// Normalize normalizes an address into the form used in known_hosts. Supports
|
||||
// IPv4, hostnames, bracketed IPv6. Any other non-standard formats are returned
|
||||
// with minimal transformation.
|
||||
func Normalize(address string) string {
|
||||
const defaultSSHPort = "22"
|
||||
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
host = address
|
||||
port = "22"
|
||||
port = defaultSSHPort
|
||||
}
|
||||
entry := host
|
||||
if port != "22" {
|
||||
entry = "[" + entry + "]:" + port
|
||||
} else if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") {
|
||||
entry = "[" + entry + "]"
|
||||
|
||||
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
||||
host = host[1 : len(host)-1]
|
||||
}
|
||||
return entry
|
||||
|
||||
if port == defaultSSHPort {
|
||||
return host
|
||||
}
|
||||
return "[" + host + "]:" + port
|
||||
}
|
||||
|
||||
// Line returns a line to add append to the known_hosts files.
|
||||
|
||||
@@ -236,7 +236,7 @@ func TestLine(t *testing.T) {
|
||||
"server.org": "server.org " + edKeyStr,
|
||||
"server.org:22": "server.org " + edKeyStr,
|
||||
"server.org:23": "[server.org]:23 " + edKeyStr,
|
||||
"[c629:1ec4:102:304:102:304:102:304]:22": "[c629:1ec4:102:304:102:304:102:304] " + edKeyStr,
|
||||
"[c629:1ec4:102:304:102:304:102:304]:22": "c629:1ec4:102:304:102:304:102:304 " + edKeyStr,
|
||||
"[c629:1ec4:102:304:102:304:102:304]:23": "[c629:1ec4:102:304:102:304:102:304]:23 " + edKeyStr,
|
||||
} {
|
||||
if got := Line([]string{in}, edKey); got != want {
|
||||
@@ -310,14 +310,25 @@ func testHostHash(t *testing.T, hostname, encoded string) {
|
||||
|
||||
func TestNormalize(t *testing.T) {
|
||||
for in, want := range map[string]string{
|
||||
"127.0.0.1:22": "127.0.0.1",
|
||||
"[127.0.0.1]:22": "127.0.0.1",
|
||||
"[127.0.0.1]:23": "[127.0.0.1]:23",
|
||||
"127.0.0.1:23": "[127.0.0.1]:23",
|
||||
"[a.b.c]:22": "a.b.c",
|
||||
"[abcd:abcd:abcd:abcd]": "[abcd:abcd:abcd:abcd]",
|
||||
"[abcd:abcd:abcd:abcd]:22": "[abcd:abcd:abcd:abcd]",
|
||||
"[abcd:abcd:abcd:abcd]:23": "[abcd:abcd:abcd:abcd]:23",
|
||||
"127.0.0.1": "127.0.0.1",
|
||||
"127.0.0.1:22": "127.0.0.1",
|
||||
"[127.0.0.1]:22": "127.0.0.1",
|
||||
"[127.0.0.1]:23": "[127.0.0.1]:23",
|
||||
"127.0.0.1:23": "[127.0.0.1]:23",
|
||||
"[a.b.c]:22": "a.b.c",
|
||||
"[a.b.c]:23": "[a.b.c]:23",
|
||||
"abcd::abcd:abcd:abcd": "abcd::abcd:abcd:abcd",
|
||||
"[abcd::abcd:abcd:abcd]": "abcd::abcd:abcd:abcd",
|
||||
"[abcd::abcd:abcd:abcd]:22": "abcd::abcd:abcd:abcd",
|
||||
"[abcd::abcd:abcd:abcd]:23": "[abcd::abcd:abcd:abcd]:23",
|
||||
"2001:db8::1": "2001:db8::1",
|
||||
"2001:db8::1:22": "2001:db8::1:22",
|
||||
"[2001:db8::1]:22": "2001:db8::1",
|
||||
"2001:db8::1:2200": "2001:db8::1:2200",
|
||||
"a.b.c.d.com:2200": "[a.b.c.d.com]:2200",
|
||||
"2001::db8:1": "2001::db8:1",
|
||||
"2001::db8:1:22": "2001::db8:1:22",
|
||||
"2001::db8:1:2200": "2001::db8:1:2200",
|
||||
} {
|
||||
got := Normalize(in)
|
||||
if got != want {
|
||||
|
||||
+29
-13
@@ -7,11 +7,13 @@ package ssh
|
||||
// Message authentication support
|
||||
|
||||
import (
|
||||
"crypto/fips140"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type macMode struct {
|
||||
@@ -46,23 +48,37 @@ func (t truncatingMAC) Size() int {
|
||||
|
||||
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
|
||||
|
||||
var macModes = map[string]*macMode{
|
||||
HMACSHA512ETM: {64, true, func(key []byte) hash.Hash {
|
||||
// macModes defines the supported MACs. MACs not included are not supported
|
||||
// and will not be negotiated, even if explicitly configured. When FIPS mode is
|
||||
// enabled, only FIPS-approved algorithms are included.
|
||||
var macModes = map[string]*macMode{}
|
||||
|
||||
func init() {
|
||||
macModes[HMACSHA512ETM] = &macMode{64, true, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha512.New, key)
|
||||
}},
|
||||
HMACSHA256ETM: {32, true, func(key []byte) hash.Hash {
|
||||
}}
|
||||
macModes[HMACSHA256ETM] = &macMode{32, true, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha256.New, key)
|
||||
}},
|
||||
HMACSHA512: {64, false, func(key []byte) hash.Hash {
|
||||
}}
|
||||
macModes[HMACSHA512] = &macMode{64, false, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha512.New, key)
|
||||
}},
|
||||
HMACSHA256: {32, false, func(key []byte) hash.Hash {
|
||||
}}
|
||||
macModes[HMACSHA256] = &macMode{32, false, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha256.New, key)
|
||||
}},
|
||||
HMACSHA1: {20, false, func(key []byte) hash.Hash {
|
||||
}}
|
||||
|
||||
if fips140.Enabled() {
|
||||
defaultMACs = slices.DeleteFunc(defaultMACs, func(algo string) bool {
|
||||
_, ok := macModes[algo]
|
||||
return !ok
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
macModes[HMACSHA1] = &macMode{20, false, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha1.New, key)
|
||||
}},
|
||||
InsecureHMACSHA196: {20, false, func(key []byte) hash.Hash {
|
||||
}}
|
||||
macModes[InsecureHMACSHA196] = &macMode{20, false, func(key []byte) hash.Hash {
|
||||
return truncatingMAC{12, hmac.New(sha1.New, key)}
|
||||
}},
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.24
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
@@ -13,23 +11,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"slices"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// After Go 1.24rc1 mlkem swapped the order of return values of Encapsulate.
|
||||
// See #70950.
|
||||
if runtime.Version() == "go1.24rc1" {
|
||||
return
|
||||
}
|
||||
supportedKexAlgos = slices.Insert(supportedKexAlgos, 0, KeyExchangeMLKEM768X25519)
|
||||
defaultKexAlgos = slices.Insert(defaultKexAlgos, 0, KeyExchangeMLKEM768X25519)
|
||||
kexAlgoMap[KeyExchangeMLKEM768X25519] = &mlkem768WithCurve25519sha256{}
|
||||
}
|
||||
|
||||
// mlkem768WithCurve25519sha256 implements the hybrid ML-KEM768 with
|
||||
// curve25519-sha256 key exchange method, as described by
|
||||
// draft-kampanakis-curdle-ssh-pq-ke-05 section 2.3.3.
|
||||
|
||||
+32
-4
@@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -43,6 +44,9 @@ type Permissions struct {
|
||||
// pass data from the authentication callbacks to the server
|
||||
// application layer.
|
||||
Extensions map[string]string
|
||||
|
||||
// ExtraData allows to store user defined data.
|
||||
ExtraData map[any]any
|
||||
}
|
||||
|
||||
type GSSAPIWithMICConfig struct {
|
||||
@@ -126,6 +130,21 @@ type ServerConfig struct {
|
||||
// Permissions.Extensions entry.
|
||||
PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
|
||||
|
||||
// VerifiedPublicKeyCallback, if non-nil, is called after a client
|
||||
// successfully confirms having control over a key that was previously
|
||||
// approved by PublicKeyCallback. The permissions object passed to the
|
||||
// callback is the one returned by PublicKeyCallback for the given public
|
||||
// key and its ownership is transferred to the callback. The returned
|
||||
// Permissions object can be the same object, optionally modified, or a
|
||||
// completely new object. If VerifiedPublicKeyCallback is non-nil,
|
||||
// PublicKeyCallback is not allowed to return a PartialSuccessError, which
|
||||
// can instead be returned by VerifiedPublicKeyCallback.
|
||||
//
|
||||
// VerifiedPublicKeyCallback does not affect which authentication methods
|
||||
// are included in the list of methods that can be attempted by the client.
|
||||
VerifiedPublicKeyCallback func(conn ConnMetadata, key PublicKey, permissions *Permissions,
|
||||
signatureAlgorithm string) (*Permissions, error)
|
||||
|
||||
// KeyboardInteractiveCallback, if non-nil, is called when
|
||||
// keyboard-interactive authentication is selected (RFC
|
||||
// 4256). The client object's Challenge function should be
|
||||
@@ -246,7 +265,7 @@ func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewCha
|
||||
fullConf.PublicKeyAuthAlgorithms = defaultPubKeyAuthAlgos
|
||||
} else {
|
||||
for _, algo := range fullConf.PublicKeyAuthAlgorithms {
|
||||
if !contains(SupportedAlgorithms().PublicKeyAuths, algo) && !contains(InsecureAlgorithms().PublicKeyAuths, algo) {
|
||||
if !slices.Contains(SupportedAlgorithms().PublicKeyAuths, algo) && !slices.Contains(InsecureAlgorithms().PublicKeyAuths, algo) {
|
||||
c.Close()
|
||||
return nil, nil, nil, fmt.Errorf("ssh: unsupported public key authentication algorithm %s", algo)
|
||||
}
|
||||
@@ -631,7 +650,7 @@ userAuthLoop:
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
algo := string(algoBytes)
|
||||
if !contains(config.PublicKeyAuthAlgorithms, underlyingAlgo(algo)) {
|
||||
if !slices.Contains(config.PublicKeyAuthAlgorithms, underlyingAlgo(algo)) {
|
||||
authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo)
|
||||
break
|
||||
}
|
||||
@@ -652,6 +671,9 @@ userAuthLoop:
|
||||
candidate.pubKeyData = pubKeyData
|
||||
candidate.perms, candidate.result = authConfig.PublicKeyCallback(s, pubKey)
|
||||
_, isPartialSuccessError := candidate.result.(*PartialSuccessError)
|
||||
if isPartialSuccessError && config.VerifiedPublicKeyCallback != nil {
|
||||
return nil, errors.New("ssh: invalid library usage: PublicKeyCallback must not return partial success when VerifiedPublicKeyCallback is defined")
|
||||
}
|
||||
|
||||
if (candidate.result == nil || isPartialSuccessError) &&
|
||||
candidate.perms != nil &&
|
||||
@@ -695,7 +717,7 @@ userAuthLoop:
|
||||
// ssh-rsa-cert-v01@openssh.com algorithm with ssh-rsa public
|
||||
// key type. The algorithm and public key type must be
|
||||
// consistent: both must be certificate algorithms, or neither.
|
||||
if !contains(algorithmsForKeyFormat(pubKey.Type()), algo) {
|
||||
if !slices.Contains(algorithmsForKeyFormat(pubKey.Type()), algo) {
|
||||
authErr = fmt.Errorf("ssh: public key type %q not compatible with selected algorithm %q",
|
||||
pubKey.Type(), algo)
|
||||
break
|
||||
@@ -705,7 +727,7 @@ userAuthLoop:
|
||||
// algorithm name that corresponds to algo with
|
||||
// sig.Format. This is usually the same, but
|
||||
// for certs, the names differ.
|
||||
if !contains(config.PublicKeyAuthAlgorithms, sig.Format) {
|
||||
if !slices.Contains(config.PublicKeyAuthAlgorithms, sig.Format) {
|
||||
authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format)
|
||||
break
|
||||
}
|
||||
@@ -722,6 +744,12 @@ userAuthLoop:
|
||||
|
||||
authErr = candidate.result
|
||||
perms = candidate.perms
|
||||
if authErr == nil && config.VerifiedPublicKeyCallback != nil {
|
||||
// Only call VerifiedPublicKeyCallback after the key has been accepted
|
||||
// and successfully verified. If authErr is non-nil, the key is not
|
||||
// considered verified and the callback must not run.
|
||||
perms, authErr = config.VerifiedPublicKeyCallback(s, pubKey, perms, algo)
|
||||
}
|
||||
}
|
||||
case "gssapi-with-mic":
|
||||
if authConfig.GSSAPIWithMICConfig == nil {
|
||||
|
||||
@@ -434,6 +434,380 @@ func TestPreAuthConnAndBanners(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifiedPublicKeyCallback(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
extraKey := "extra"
|
||||
extraDataString := "just a string"
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||
if permissions != nil && permissions.ExtraData != nil {
|
||||
if !reflect.DeepEqual(map[any]any{extraKey: extraDataString}, permissions.ExtraData) {
|
||||
t.Errorf("expected extra data: %v; got: %v", extraDataString, permissions.ExtraData)
|
||||
}
|
||||
} else {
|
||||
t.Error("expected extra data is missing")
|
||||
}
|
||||
if signatureAlgorithm != KeyAlgoRSASHA256 {
|
||||
t.Errorf("expected signature algorithm: %q; got: %q", KeyAlgoRSASHA256, signatureAlgorithm)
|
||||
}
|
||||
return permissions, nil
|
||||
},
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
return &Permissions{ExtraData: map[any]any{extraKey: extraDataString}}, nil
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
conn, _, _, err := NewServerConn(c1, serverConf)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected server error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(map[any]any{extraKey: extraDataString}, conn.Permissions.ExtraData) {
|
||||
t.Errorf("expected extra data: %v; got: %v", extraDataString, conn.Permissions.ExtraData)
|
||||
}
|
||||
}()
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestVerifiedPublicCallbackPartialSuccess(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, &PartialSuccessError{
|
||||
Next: ServerAuthCallbacks{
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
if string(password) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
Password(clientPassword),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
go NewServerConn(c1, serverConf)
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||
if err != nil {
|
||||
t.Fatalf("client login error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifiedPublicKeyCallbackPwdAndKey(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
if string(password) == clientPassword {
|
||||
return nil, &PartialSuccessError{
|
||||
Next: ServerAuthCallbacks{
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
|
||||
},
|
||||
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
Auth: []AuthMethod{
|
||||
Password(clientPassword),
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
go NewServerConn(c1, serverConf)
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||
if err != nil {
|
||||
t.Fatalf("client login error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifiedPubKeyCallbackAuthMethods(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
return nil, nil
|
||||
},
|
||||
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
go NewServerConn(c1, serverConf)
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||
if err == nil {
|
||||
t.Fatal("client login succeed with only VerifiedPublicKeyCallback defined")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifiedPubKeyCallbackError(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
return nil, nil
|
||||
},
|
||||
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
go NewServerConn(c1, serverConf)
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||
if err == nil {
|
||||
t.Fatal("client login succeed with VerifiedPublicKeyCallback returning an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifiedPublicCallbackPartialSuccessBadUsage(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
// Returning PartialSuccessError is not permitted when
|
||||
// VerifiedPublicKeyCallback is defined. This callback is
|
||||
// invoked for both query requests and real authentications,
|
||||
// while VerifiedPublicKeyCallback is only triggered if the
|
||||
// client has proven control of the key.
|
||||
return nil, &PartialSuccessError{
|
||||
Next: ServerAuthCallbacks{
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
if string(password) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, &PartialSuccessError{
|
||||
Next: ServerAuthCallbacks{
|
||||
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
|
||||
if string(password) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil, errors.New("invalid credentials")
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
Password(clientPassword),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
go NewServerConn(c1, serverConf)
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||
if err == nil {
|
||||
t.Fatal("authentication suceeded with PartialSuccess returned from PublicKeyCallback and VerifiedPublicKeyCallback defined")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifiedPublicKeyCallbackOnError(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
var verifiedCallbackCalled bool
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||
verifiedCallbackCalled = true
|
||||
return nil, nil
|
||||
},
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
return nil, errors.New("invalid key")
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
NewServerConn(c1, serverConf)
|
||||
}()
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||
if err == nil {
|
||||
t.Fatal("authentication should fail")
|
||||
}
|
||||
<-done
|
||||
if verifiedCallbackCalled {
|
||||
t.Error("VerifiedPublicKeyCallback called after PublicKeyCallback returned an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifiedPublicKeyCallbackOnly(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
serverConf := &ServerConfig{
|
||||
VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
NewServerConn(c1, serverConf)
|
||||
}()
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||
if err == nil {
|
||||
t.Fatal("authentication suceeded with only VerifiedPublicKeyCallback defined")
|
||||
}
|
||||
<-done
|
||||
}
|
||||
|
||||
type markerConn struct {
|
||||
closed uint32
|
||||
used uint32
|
||||
|
||||
@@ -4,4 +4,6 @@
|
||||
|
||||
// Package test contains integration tests for the
|
||||
// golang.org/x/crypto/ssh package.
|
||||
//
|
||||
// Deprecated: this package is for internal use only.
|
||||
package test
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
@@ -254,6 +255,9 @@ var (
|
||||
// (to setup server->client keys) or clientKeys (for client->server keys).
|
||||
func newPacketCipher(d direction, algs DirectionAlgorithms, kex *kexResult) (packetCipher, error) {
|
||||
cipherMode := cipherModes[algs.Cipher]
|
||||
if cipherMode == nil {
|
||||
return nil, fmt.Errorf("ssh: unsupported cipher %v", algs.Cipher)
|
||||
}
|
||||
|
||||
iv := make([]byte, cipherMode.ivSize)
|
||||
key := make([]byte, cipherMode.keySize)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,897 @@
|
||||
// Code generated by gen_fallback_bundle.go; DO NOT EDIT.
|
||||
|
||||
package bundle
|
||||
|
||||
var unparsedCertificates = []unparsedCertificate{
|
||||
{
|
||||
cn: "CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS,OU=Ceres,O=FNMT-RCM,C=ES,2.5.4.97=#130f56415445532d51323832363030344a",
|
||||
sha256Hash: "554153b13d2cf9ddb753bfbe1a4e0ae08d0aa4187058fe60a2b862b2e4b87bcb",
|
||||
certStartOff: 0,
|
||||
certLength: 626,
|
||||
},
|
||||
{
|
||||
cn: "CN=ACCVRAIZ1,OU=PKIACCV,O=ACCV,C=ES",
|
||||
sha256Hash: "9a6ec012e1a7da9dbe34194d478ad7c0db1822fb071df12981496ed104384113",
|
||||
certStartOff: 626,
|
||||
certLength: 2007,
|
||||
},
|
||||
{
|
||||
cn: "CN=Actalis Authentication Root CA,O=Actalis S.p.A./03358520967,L=Milan,C=IT",
|
||||
sha256Hash: "55926084ec963a64b96e2abe01ce0ba86a64fbfebcc7aab5afc155b37fd76066",
|
||||
certStartOff: 2633,
|
||||
certLength: 1471,
|
||||
},
|
||||
{
|
||||
cn: "CN=AffirmTrust Commercial,O=AffirmTrust,C=US",
|
||||
sha256Hash: "0376ab1d54c5f9803ce4b2e201a0ee7eef7b57b636e8a93c9b8d4860c96f5fa7",
|
||||
certStartOff: 4104,
|
||||
certLength: 848,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=AffirmTrust Networking,O=AffirmTrust,C=US",
|
||||
sha256Hash: "0a81ec5a929777f145904af38d5d509f66b5e2c58fcdb531058b0e17f3f0b41b",
|
||||
certStartOff: 4952,
|
||||
certLength: 848,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=AffirmTrust Premium ECC,O=AffirmTrust,C=US",
|
||||
sha256Hash: "bd71fdf6da97e4cf62d1647add2581b07d79adf8397eb4ecba9c5e8488821423",
|
||||
certStartOff: 5800,
|
||||
certLength: 514,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=AffirmTrust Premium,O=AffirmTrust,C=US",
|
||||
sha256Hash: "70a73f7f376b60074248904534b11482d5bf0e698ecc498df52577ebf2e93b9a",
|
||||
certStartOff: 6314,
|
||||
certLength: 1354,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=Amazon Root CA 1,O=Amazon,C=US",
|
||||
sha256Hash: "8ecde6884f3d87b1125ba31ac3fcb13d7016de7f57cc904fe1cb97c6ae98196e",
|
||||
certStartOff: 7668,
|
||||
certLength: 837,
|
||||
},
|
||||
{
|
||||
cn: "CN=Amazon Root CA 2,O=Amazon,C=US",
|
||||
sha256Hash: "1ba5b2aa8c65401a82960118f80bec4f62304d83cec4713a19c39c011ea46db4",
|
||||
certStartOff: 8505,
|
||||
certLength: 1349,
|
||||
},
|
||||
{
|
||||
cn: "CN=Amazon Root CA 3,O=Amazon,C=US",
|
||||
sha256Hash: "18ce6cfe7bf14e60b2e347b8dfe868cb31d02ebb3ada271569f50343b46db3a4",
|
||||
certStartOff: 9854,
|
||||
certLength: 442,
|
||||
},
|
||||
{
|
||||
cn: "CN=Amazon Root CA 4,O=Amazon,C=US",
|
||||
sha256Hash: "e35d28419ed02025cfa69038cd623962458da5c695fbdea3c22b0bfb25897092",
|
||||
certStartOff: 10296,
|
||||
certLength: 502,
|
||||
},
|
||||
{
|
||||
cn: "CN=Atos TrustedRoot 2011,O=Atos,C=DE",
|
||||
sha256Hash: "f356bea244b7a91eb35d53ca9ad7864ace018e2d35d5f8f96ddf68a6f41aa474",
|
||||
certStartOff: 10798,
|
||||
certLength: 891,
|
||||
},
|
||||
{
|
||||
cn: "CN=Atos TrustedRoot Root CA ECC TLS 2021,O=Atos,C=DE",
|
||||
sha256Hash: "b2fae53e14ccd7ab9212064701ae279c1d8988facb775fa8a008914e663988a8",
|
||||
certStartOff: 11689,
|
||||
certLength: 537,
|
||||
},
|
||||
{
|
||||
cn: "CN=Atos TrustedRoot Root CA RSA TLS 2021,O=Atos,C=DE",
|
||||
sha256Hash: "81a9088ea59fb364c548a6f85559099b6f0405efbf18e5324ec9f457ba00112f",
|
||||
certStartOff: 12226,
|
||||
certLength: 1384,
|
||||
},
|
||||
{
|
||||
cn: "CN=Autoridad de Certificacion Firmaprofesional CIF A62634068,C=ES",
|
||||
sha256Hash: "57de0583efd2b26e0361da99da9df4648def7ee8441c3b728afa9bcde0f9b26a",
|
||||
certStartOff: 13610,
|
||||
certLength: 1560,
|
||||
},
|
||||
{
|
||||
cn: "CN=BJCA Global Root CA1,O=BEIJING CERTIFICATE AUTHORITY,C=CN",
|
||||
sha256Hash: "f3896f88fe7c0a882766a7fa6ad2749fb57a7f3e98fb769c1fa7b09c2c44d5ae",
|
||||
certStartOff: 15170,
|
||||
certLength: 1400,
|
||||
},
|
||||
{
|
||||
cn: "CN=BJCA Global Root CA2,O=BEIJING CERTIFICATE AUTHORITY,C=CN",
|
||||
sha256Hash: "574df6931e278039667b720afdc1600fc27eb66dd3092979fb73856487212882",
|
||||
certStartOff: 16570,
|
||||
certLength: 553,
|
||||
},
|
||||
{
|
||||
cn: "CN=Buypass Class 2 Root CA,O=Buypass AS-983163327,C=NO",
|
||||
sha256Hash: "9a114025197c5bb95d94e63d55cd43790847b646b23cdf11ada4a00eff15fb48",
|
||||
certStartOff: 17123,
|
||||
certLength: 1373,
|
||||
},
|
||||
{
|
||||
cn: "CN=Buypass Class 3 Root CA,O=Buypass AS-983163327,C=NO",
|
||||
sha256Hash: "edf7ebbca27a2a384d387b7d4010c666e2edb4843e4c29b4ae1d5b9332e6b24d",
|
||||
certStartOff: 18496,
|
||||
certLength: 1373,
|
||||
},
|
||||
{
|
||||
cn: "CN=CA Disig Root R2,O=Disig a.s.,L=Bratislava,C=SK",
|
||||
sha256Hash: "e23d4a036d7b70e9f595b1422079d2b91edfbb1fb651a0633eaa8a9dc5f80703",
|
||||
certStartOff: 19869,
|
||||
certLength: 1389,
|
||||
},
|
||||
{
|
||||
cn: "CN=CFCA EV ROOT,O=China Financial Certification Authority,C=CN",
|
||||
sha256Hash: "5cc3d78e4e1d5e45547a04e6873e64f90cf9536d1ccc2ef800f355c4c5fd70fd",
|
||||
certStartOff: 21258,
|
||||
certLength: 1425,
|
||||
},
|
||||
{
|
||||
cn: "CN=COMODO Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB",
|
||||
sha256Hash: "0c2cd63df7806fa399ede809116b575bf87989f06518f9808c860503178baf66",
|
||||
certStartOff: 22683,
|
||||
certLength: 1057,
|
||||
},
|
||||
{
|
||||
cn: "CN=COMODO ECC Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB",
|
||||
sha256Hash: "1793927a0614549789adce2f8f34f7f0b66d0f3ae3a3b84d21ec15dbba4fadc7",
|
||||
certStartOff: 23740,
|
||||
certLength: 653,
|
||||
},
|
||||
{
|
||||
cn: "CN=COMODO RSA Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB",
|
||||
sha256Hash: "52f0e1c4e58ec629291b60317f074671b85d7ea80d5b07273463534b32b40234",
|
||||
certStartOff: 24393,
|
||||
certLength: 1500,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certainly Root E1,O=Certainly,C=US",
|
||||
sha256Hash: "b4585f22e4ac756a4e8612a1361c5d9d031a93fd84febb778fa3068b0fc42dc2",
|
||||
certStartOff: 25893,
|
||||
certLength: 507,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certainly Root R1,O=Certainly,C=US",
|
||||
sha256Hash: "77b82cd8644c4305f7acc5cb156b45675004033d51c60c6202a8e0c33467d3a0",
|
||||
certStartOff: 26400,
|
||||
certLength: 1355,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certigna Root CA,OU=0002 48146308100036,O=Dhimyotis,C=FR",
|
||||
sha256Hash: "d48d3d23eedb50a459e55197601c27774b9d7b18c94d5a059511a10250b93168",
|
||||
certStartOff: 27755,
|
||||
certLength: 1631,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certigna,O=Dhimyotis,C=FR",
|
||||
sha256Hash: "e3b6a2db2ed7ce48842f7ac53241c7b71d54144bfb40c11f3f1d0b42f5eea12d",
|
||||
certStartOff: 29386,
|
||||
certLength: 940,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certum EC-384 CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL",
|
||||
sha256Hash: "6b328085625318aa50d173c98d8bda09d57e27413d114cf787a0f5d06c030cf6",
|
||||
certStartOff: 30326,
|
||||
certLength: 617,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certum Trusted Network CA 2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL",
|
||||
sha256Hash: "b676f2eddae8775cd36cb0f63cd1d4603961f49e6265ba013a2f0307b6d0b804",
|
||||
certStartOff: 30943,
|
||||
certLength: 1494,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certum Trusted Network CA,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL",
|
||||
sha256Hash: "5c58468d55f58e497e743982d2b50010b6d165374acf83a7d4a32db768c4408e",
|
||||
certStartOff: 32437,
|
||||
certLength: 959,
|
||||
},
|
||||
{
|
||||
cn: "CN=Certum Trusted Root CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL",
|
||||
sha256Hash: "fe7696573855773e37a95e7ad4d9cc96c30157c15d31765ba9b15704e1ae78fd",
|
||||
certStartOff: 33396,
|
||||
certLength: 1476,
|
||||
},
|
||||
{
|
||||
cn: "CN=CommScope Public Trust ECC Root-01,O=CommScope,C=US",
|
||||
sha256Hash: "11437cda7bb45e41365f45b39a38986b0de00def348e0c7bb0873633800bc38b",
|
||||
certStartOff: 34872,
|
||||
certLength: 545,
|
||||
},
|
||||
{
|
||||
cn: "CN=CommScope Public Trust ECC Root-02,O=CommScope,C=US",
|
||||
sha256Hash: "2ffb7f813bbbb3c89ab4e8162d0f16d71509a830cc9d73c262e5140875d1ad4a",
|
||||
certStartOff: 35417,
|
||||
certLength: 544,
|
||||
},
|
||||
{
|
||||
cn: "CN=CommScope Public Trust RSA Root-01,O=CommScope,C=US",
|
||||
sha256Hash: "02bdf96e2a45dd9bf18fc7e1dbdf21a0379ba3c9c2610344cfd8d606fec1ed81",
|
||||
certStartOff: 35961,
|
||||
certLength: 1392,
|
||||
},
|
||||
{
|
||||
cn: "CN=CommScope Public Trust RSA Root-02,O=CommScope,C=US",
|
||||
sha256Hash: "ffe943d793424b4f7c440c1c3d648d5363f34b82dc87aa7a9f118fc5dee101f1",
|
||||
certStartOff: 37353,
|
||||
certLength: 1392,
|
||||
},
|
||||
{
|
||||
cn: "CN=D-TRUST BR Root CA 1 2020,O=D-Trust GmbH,C=DE",
|
||||
sha256Hash: "e59aaa816009c22bff5b25bad37df306f049797c1f81d85ab089e657bd8f0044",
|
||||
certStartOff: 38745,
|
||||
certLength: 735,
|
||||
},
|
||||
{
|
||||
cn: "CN=D-TRUST BR Root CA 2 2023,O=D-Trust GmbH,C=DE",
|
||||
sha256Hash: "0552e6f83fdf65e8fa9670e666df28a4e21340b510cbe52566f97c4fb94b2bd1",
|
||||
certStartOff: 39480,
|
||||
certLength: 1453,
|
||||
},
|
||||
{
|
||||
cn: "CN=D-TRUST EV Root CA 1 2020,O=D-Trust GmbH,C=DE",
|
||||
sha256Hash: "08170d1aa36453901a2f959245e347db0c8d37abaabc56b81aa100dc958970db",
|
||||
certStartOff: 40933,
|
||||
certLength: 735,
|
||||
},
|
||||
{
|
||||
cn: "CN=D-TRUST EV Root CA 2 2023,O=D-Trust GmbH,C=DE",
|
||||
sha256Hash: "8e8221b2e7d4007836a1672f0dcc299c33bc07d316f132fa1a206d587150f1ce",
|
||||
certStartOff: 41668,
|
||||
certLength: 1453,
|
||||
},
|
||||
{
|
||||
cn: "CN=D-TRUST Root Class 3 CA 2 2009,O=D-Trust GmbH,C=DE",
|
||||
sha256Hash: "49e7a442acf0ea6287050054b52564b650e4f49e42e348d6aa38e039e957b1c1",
|
||||
certStartOff: 43121,
|
||||
certLength: 1079,
|
||||
},
|
||||
{
|
||||
cn: "CN=D-TRUST Root Class 3 CA 2 EV 2009,O=D-Trust GmbH,C=DE",
|
||||
sha256Hash: "eec5496b988ce98625b934092eec2908bed0b0f316c2d4730c84eaf1f3d34881",
|
||||
certStartOff: 44200,
|
||||
certLength: 1095,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Assured ID Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "3e9099b5015e8f486c00bcea9d111ee721faba355a89bcf1df69561e3dc6325c",
|
||||
certStartOff: 45295,
|
||||
certLength: 955,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Assured ID Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "7d05ebb682339f8c9451ee094eebfefa7953a114edb2f44949452fab7d2fc185",
|
||||
certStartOff: 46250,
|
||||
certLength: 922,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Assured ID Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "7e37cb8b4c47090cab36551ba6f45db840680fba166a952db100717f43053fc2",
|
||||
certStartOff: 47172,
|
||||
certLength: 586,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161",
|
||||
certStartOff: 47758,
|
||||
certLength: 947,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Global Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "cb3ccbb76031e5e0138f8dd39a23f9de47ffc35e43c1144cea27d46a5ab1cb5f",
|
||||
certStartOff: 48705,
|
||||
certLength: 914,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Global Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "31ad6648f8104138c738f39ea4320133393e3a18cc02296ef97c2ac9ef6731d0",
|
||||
certStartOff: 49619,
|
||||
certLength: 579,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert High Assurance EV Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "7431e5f4c3c1ce4690774f0b61e05440883ba9a01ed00ba6abd7806ed3b118cf",
|
||||
certStartOff: 50198,
|
||||
certLength: 969,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert TLS ECC P384 Root G5,O=DigiCert\\, Inc.,C=US",
|
||||
sha256Hash: "018e13f0772532cf809bd1b17281867283fc48c6e13be9c69812854a490c1b05",
|
||||
certStartOff: 51167,
|
||||
certLength: 541,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert TLS RSA4096 Root G5,O=DigiCert\\, Inc.,C=US",
|
||||
sha256Hash: "371a00dc0533b3721a7eeb40e8419e70799d2b0a0f2c1d80693165f7cec4ad75",
|
||||
certStartOff: 51708,
|
||||
certLength: 1386,
|
||||
},
|
||||
{
|
||||
cn: "CN=DigiCert Trusted Root G4,OU=www.digicert.com,O=DigiCert Inc,C=US",
|
||||
sha256Hash: "552f7bdcf1a7af9e6ce672017f4f12abf77240c78e761ac203d1d9d20ac89988",
|
||||
certStartOff: 53094,
|
||||
certLength: 1428,
|
||||
},
|
||||
{
|
||||
cn: "CN=Entrust Root Certification Authority - EC1,OU=See www.entrust.net/legal-terms+OU=(c) 2012 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US",
|
||||
sha256Hash: "02ed0eb28c14da45165c566791700d6451d7fb56f0b2ab1d3b8eb070e56edff5",
|
||||
certStartOff: 54522,
|
||||
certLength: 765,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=Entrust Root Certification Authority - G2,OU=See www.entrust.net/legal-terms+OU=(c) 2009 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US",
|
||||
sha256Hash: "43df5774b03e7fef5fe40d931a7bedf1bb2e6b42738c4e6d3841103d3aa7f339",
|
||||
certStartOff: 55287,
|
||||
certLength: 1090,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=Entrust Root Certification Authority,OU=www.entrust.net/CPS is incorporated by reference+OU=(c) 2006 Entrust\\, Inc.,O=Entrust\\, Inc.,C=US",
|
||||
sha256Hash: "73c176434f1bc6d5adf45b0e76e727287c8de57616c1e6e6141a2b2cbc7d8e4c",
|
||||
certStartOff: 56377,
|
||||
certLength: 1173,
|
||||
distrustAfter: "2024-11-30T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=FIRMAPROFESIONAL CA ROOT-A WEB,O=Firmaprofesional SA,C=ES,2.5.4.97=#130f56415445532d413632363334303638",
|
||||
sha256Hash: "bef256daf26e9c69bdec1602359798f3caf71821a03e018257c53c65617f3d4a",
|
||||
certStartOff: 57550,
|
||||
certLength: 638,
|
||||
},
|
||||
{
|
||||
cn: "CN=GDCA TrustAUTH R5 ROOT,O=GUANG DONG CERTIFICATE AUTHORITY CO.\\,LTD.,C=CN",
|
||||
sha256Hash: "bfff8fd04433487d6a8aa60c1a29767a9fc2bbb05e420f713a13b992891d3893",
|
||||
certStartOff: 58188,
|
||||
certLength: 1420,
|
||||
},
|
||||
{
|
||||
cn: "CN=GLOBALTRUST 2020,O=e-commerce monitoring GmbH,C=AT",
|
||||
sha256Hash: "9a296a5182d1d451a2e37f439b74daafa267523329f90f9a0d2007c334e23c9a",
|
||||
certStartOff: 59608,
|
||||
certLength: 1414,
|
||||
distrustAfter: "2024-06-30T00:00:00Z",
|
||||
},
|
||||
{
|
||||
cn: "CN=GTS Root R1,O=Google Trust Services LLC,C=US",
|
||||
sha256Hash: "d947432abde7b7fa90fc2e6b59101b1280e0e1c7e4e40fa3c6887fff57a7f4cf",
|
||||
certStartOff: 61022,
|
||||
certLength: 1371,
|
||||
},
|
||||
{
|
||||
cn: "CN=GTS Root R2,O=Google Trust Services LLC,C=US",
|
||||
sha256Hash: "8d25cd97229dbf70356bda4eb3cc734031e24cf00fafcfd32dc76eb5841c7ea8",
|
||||
certStartOff: 62393,
|
||||
certLength: 1371,
|
||||
},
|
||||
{
|
||||
cn: "CN=GTS Root R3,O=Google Trust Services LLC,C=US",
|
||||
sha256Hash: "34d8a73ee208d9bcdb0d956520934b4e40e69482596e8b6f73c8426b010a6f48",
|
||||
certStartOff: 63764,
|
||||
certLength: 525,
|
||||
},
|
||||
{
|
||||
cn: "CN=GTS Root R4,O=Google Trust Services LLC,C=US",
|
||||
sha256Hash: "349dfa4058c5e263123b398ae795573c4e1313c83fe68f93556cd5e8031b3c7d",
|
||||
certStartOff: 64289,
|
||||
certLength: 525,
|
||||
},
|
||||
{
|
||||
cn: "CN=GlobalSign Root E46,O=GlobalSign nv-sa,C=BE",
|
||||
sha256Hash: "cbb9c44d84b8043e1050ea31a69f514955d7bfd2e2c6b49301019ad61d9f5058",
|
||||
certStartOff: 64814,
|
||||
certLength: 527,
|
||||
},
|
||||
{
|
||||
cn: "CN=GlobalSign Root R46,O=GlobalSign nv-sa,C=BE",
|
||||
sha256Hash: "4fa3126d8d3a11d1c4855a4f807cbad6cf919d3a5a88b03bea2c6372d93c40c9",
|
||||
certStartOff: 65341,
|
||||
certLength: 1374,
|
||||
},
|
||||
{
|
||||
cn: "CN=GlobalSign,OU=GlobalSign ECC Root CA - R4,O=GlobalSign",
|
||||
sha256Hash: "b085d70b964f191a73e4af0d54ae7a0e07aafdaf9b71dd0862138ab7325a24a2",
|
||||
certStartOff: 66715,
|
||||
certLength: 480,
|
||||
},
|
||||
{
|
||||
cn: "CN=GlobalSign,OU=GlobalSign ECC Root CA - R5,O=GlobalSign",
|
||||
sha256Hash: "179fbc148a3dd00fd24ea13458cc43bfa7f59c8182d783a513f6ebec100c8924",
|
||||
certStartOff: 67195,
|
||||
certLength: 546,
|
||||
},
|
||||
{
|
||||
cn: "CN=GlobalSign,OU=GlobalSign Root CA - R3,O=GlobalSign",
|
||||
sha256Hash: "cbb522d7b7f127ad6a0113865bdf1cd4102e7d0759af635a7cf4720dc963c53b",
|
||||
certStartOff: 67741,
|
||||
certLength: 867,
|
||||
},
|
||||
{
|
||||
cn: "CN=GlobalSign,OU=GlobalSign Root CA - R6,O=GlobalSign",
|
||||
sha256Hash: "2cabeafe37d06ca22aba7391c0033d25982952c453647349763a3ab5ad6ccf69",
|
||||
certStartOff: 68608,
|
||||
certLength: 1415,
|
||||
},
|
||||
{
|
||||
cn: "CN=Go Daddy Root Certificate Authority - G2,O=GoDaddy.com\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
|
||||
sha256Hash: "45140b3247eb9cc8c5b4f0d7b53091f73292089e6e5a63e2749dd3aca9198eda",
|
||||
certStartOff: 70023,
|
||||
certLength: 969,
|
||||
},
|
||||
{
|
||||
cn: "CN=HARICA TLS ECC Root CA 2021,O=Hellenic Academic and Research Institutions CA,C=GR",
|
||||
sha256Hash: "3f99cc474acfce4dfed58794665e478d1547739f2e780f1bb4ca9b133097d401",
|
||||
certStartOff: 70992,
|
||||
certLength: 600,
|
||||
},
|
||||
{
|
||||
cn: "CN=HARICA TLS RSA Root CA 2021,O=Hellenic Academic and Research Institutions CA,C=GR",
|
||||
sha256Hash: "d95d0e8eda79525bf9beb11b14d2100d3294985f0c62d9fabd9cd999eccb7b1d",
|
||||
certStartOff: 71592,
|
||||
certLength: 1448,
|
||||
},
|
||||
{
|
||||
cn: "CN=Hellenic Academic and Research Institutions ECC RootCA 2015,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR",
|
||||
sha256Hash: "44b545aa8a25e65a73ca15dc27fc36d24c1cb9953a066539b11582dc487b4833",
|
||||
certStartOff: 73040,
|
||||
certLength: 711,
|
||||
},
|
||||
{
|
||||
cn: "CN=Hellenic Academic and Research Institutions RootCA 2015,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR",
|
||||
sha256Hash: "a040929a02ce53b4acf4f2ffc6981ce4496f755e6d45fe0b2a692bcd52523f36",
|
||||
certStartOff: 73751,
|
||||
certLength: 1551,
|
||||
},
|
||||
{
|
||||
cn: "CN=HiPKI Root CA - G1,O=Chunghwa Telecom Co.\\, Ltd.,C=TW",
|
||||
sha256Hash: "f015ce3cc239bfef064be9f1d2c417e1a0264a0a94be1f0c8d121864eb6949cc",
|
||||
certStartOff: 75302,
|
||||
certLength: 1390,
|
||||
},
|
||||
{
|
||||
cn: "CN=Hongkong Post Root CA 3,O=Hongkong Post,L=Hong Kong,ST=Hong Kong,C=HK",
|
||||
sha256Hash: "5a2fc03f0c83b090bbfa40604b0988446c7636183df9846e17101a447fb8efd6",
|
||||
certStartOff: 76692,
|
||||
certLength: 1491,
|
||||
},
|
||||
{
|
||||
cn: "CN=ISRG Root X1,O=Internet Security Research Group,C=US",
|
||||
sha256Hash: "96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6",
|
||||
certStartOff: 78183,
|
||||
certLength: 1391,
|
||||
},
|
||||
{
|
||||
cn: "CN=ISRG Root X2,O=Internet Security Research Group,C=US",
|
||||
sha256Hash: "69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470",
|
||||
certStartOff: 79574,
|
||||
certLength: 543,
|
||||
},
|
||||
{
|
||||
cn: "CN=IdenTrust Commercial Root CA 1,O=IdenTrust,C=US",
|
||||
sha256Hash: "5d56499be4d2e08bcfcad08a3e38723d50503bde706948e42f55603019e528ae",
|
||||
certStartOff: 80117,
|
||||
certLength: 1380,
|
||||
},
|
||||
{
|
||||
cn: "CN=IdenTrust Public Sector Root CA 1,O=IdenTrust,C=US",
|
||||
sha256Hash: "30d0895a9a448a262091635522d1f52010b5867acae12c78ef958fd4f4389f2f",
|
||||
certStartOff: 81497,
|
||||
certLength: 1386,
|
||||
},
|
||||
{
|
||||
cn: "CN=Izenpe.com,O=IZENPE S.A.,C=ES",
|
||||
sha256Hash: "2530cc8e98321502bad96f9b1fba1b099e2d299e0f4548bb914f363bc0d4531f",
|
||||
certStartOff: 82883,
|
||||
certLength: 1525,
|
||||
},
|
||||
{
|
||||
cn: "CN=Microsec e-Szigno Root CA 2009,O=Microsec Ltd.,L=Budapest,C=HU,1.2.840.113549.1.9.1=#0c10696e666f40652d737a69676e6f2e6875",
|
||||
sha256Hash: "3c5f81fea5fab82c64bfa2eaecafcde8e077fc8620a7cae537163df36edbf378",
|
||||
certStartOff: 84408,
|
||||
certLength: 1038,
|
||||
},
|
||||
{
|
||||
cn: "CN=Microsoft ECC Root Certificate Authority 2017,O=Microsoft Corporation,C=US",
|
||||
sha256Hash: "358df39d764af9e1b766e9c972df352ee15cfac227af6ad1d70e8e4a6edcba02",
|
||||
certStartOff: 85446,
|
||||
certLength: 605,
|
||||
},
|
||||
{
|
||||
cn: "CN=Microsoft RSA Root Certificate Authority 2017,O=Microsoft Corporation,C=US",
|
||||
sha256Hash: "c741f70f4b2a8d88bf2e71c14122ef53ef10eba0cfa5e64cfa20f418853073e0",
|
||||
certStartOff: 86051,
|
||||
certLength: 1452,
|
||||
},
|
||||
{
|
||||
cn: "CN=NAVER Global Root Certification Authority,O=NAVER BUSINESS PLATFORM Corp.,C=KR",
|
||||
sha256Hash: "88f438dcf8ffd1fa8f429115ffe5f82ae1e06e0c70c375faad717b34a49e7265",
|
||||
certStartOff: 87503,
|
||||
certLength: 1446,
|
||||
},
|
||||
{
|
||||
cn: "CN=NetLock Arany (Class Gold) Főtanúsítvány,OU=Tanúsítványkiadók (Certification Services),O=NetLock Kft.,L=Budapest,C=HU",
|
||||
sha256Hash: "6c61dac3a2def031506be036d2a6fe401994fbd13df9c8d466599274c446ec98",
|
||||
certStartOff: 88949,
|
||||
certLength: 1049,
|
||||
},
|
||||
{
|
||||
cn: "CN=OISTE Server Root ECC G1,O=OISTE Foundation,C=CH",
|
||||
sha256Hash: "eec997c0c30f216f7e3b8b307d2bae42412d753fc8219dafd1520b2572850f49",
|
||||
certStartOff: 89998,
|
||||
certLength: 569,
|
||||
},
|
||||
{
|
||||
cn: "CN=OISTE Server Root RSA G1,O=OISTE Foundation,C=CH",
|
||||
sha256Hash: "9ae36232a5189ffddb353dfd26520c015395d22777dac59db57b98c089a651e6",
|
||||
certStartOff: 90567,
|
||||
certLength: 1415,
|
||||
},
|
||||
{
|
||||
cn: "CN=OISTE WISeKey Global Root GB CA,OU=OISTE Foundation Endorsed,O=WISeKey,C=CH",
|
||||
sha256Hash: "6b9c08e86eb0f767cfad65cd98b62149e5494a67f5845e7bd1ed019f27b86bd6",
|
||||
certStartOff: 91982,
|
||||
certLength: 953,
|
||||
},
|
||||
{
|
||||
cn: "CN=OISTE WISeKey Global Root GC CA,OU=OISTE Foundation Endorsed,O=WISeKey,C=CH",
|
||||
sha256Hash: "8560f91c3624daba9570b5fea0dbe36ff11a8323be9486854fb3f34a5571198d",
|
||||
certStartOff: 92935,
|
||||
certLength: 621,
|
||||
},
|
||||
{
|
||||
cn: "CN=QuoVadis Root CA 1 G3,O=QuoVadis Limited,C=BM",
|
||||
sha256Hash: "8a866fd1b276b57e578e921c65828a2bed58e9f2f288054134b7f1f4bfc9cc74",
|
||||
certStartOff: 93556,
|
||||
certLength: 1380,
|
||||
},
|
||||
{
|
||||
cn: "CN=QuoVadis Root CA 2 G3,O=QuoVadis Limited,C=BM",
|
||||
sha256Hash: "8fe4fb0af93a4d0d67db0bebb23e37c71bf325dcbcdd240ea04daf58b47e1840",
|
||||
certStartOff: 94936,
|
||||
certLength: 1380,
|
||||
},
|
||||
{
|
||||
cn: "CN=QuoVadis Root CA 2,O=QuoVadis Limited,C=BM",
|
||||
sha256Hash: "85a0dd7dd720adb7ff05f83d542b209dc7ff4528f7d677b18389fea5e5c49e86",
|
||||
certStartOff: 96316,
|
||||
certLength: 1467,
|
||||
},
|
||||
{
|
||||
cn: "CN=QuoVadis Root CA 3 G3,O=QuoVadis Limited,C=BM",
|
||||
sha256Hash: "88ef81de202eb018452e43f864725cea5fbd1fc2d9d205730709c5d8b8690f46",
|
||||
certStartOff: 97783,
|
||||
certLength: 1380,
|
||||
},
|
||||
{
|
||||
cn: "CN=QuoVadis Root CA 3,O=QuoVadis Limited,C=BM",
|
||||
sha256Hash: "18f1fc7f205df8adddeb7fe007dd57e3af375a9c4d8d73546bf4f1fed1e18d35",
|
||||
certStartOff: 99163,
|
||||
certLength: 1697,
|
||||
},
|
||||
{
|
||||
cn: "CN=SSL.com EV Root Certification Authority ECC,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
||||
sha256Hash: "22a2c1f7bded704cc1e701b5f408c310880fe956b5de2a4a44f99c873a25a7c8",
|
||||
certStartOff: 100860,
|
||||
certLength: 664,
|
||||
},
|
||||
{
|
||||
cn: "CN=SSL.com EV Root Certification Authority RSA R2,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
||||
sha256Hash: "2e7bf16cc22485a7bbe2aa8696750761b0ae39be3b2fe9d0cc6d4ef73491425c",
|
||||
certStartOff: 101524,
|
||||
certLength: 1519,
|
||||
},
|
||||
{
|
||||
cn: "CN=SSL.com Root Certification Authority ECC,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
||||
sha256Hash: "3417bb06cc6007da1b961c920b8ab4ce3fad820e4aa30b9acbc4a74ebdcebc65",
|
||||
certStartOff: 103043,
|
||||
certLength: 657,
|
||||
},
|
||||
{
|
||||
cn: "CN=SSL.com Root Certification Authority RSA,O=SSL Corporation,L=Houston,ST=Texas,C=US",
|
||||
sha256Hash: "85666a562ee0be5ce925c1d8890a6f76a87ec16d4d7d5f29ea7419cf20123b69",
|
||||
certStartOff: 103700,
|
||||
certLength: 1505,
|
||||
},
|
||||
{
|
||||
cn: "CN=SSL.com TLS ECC Root CA 2022,O=SSL Corporation,C=US",
|
||||
sha256Hash: "c32ffd9f46f936d16c3673990959434b9ad60aafbb9e7cf33654f144cc1ba143",
|
||||
certStartOff: 105205,
|
||||
certLength: 574,
|
||||
},
|
||||
{
|
||||
cn: "CN=SSL.com TLS RSA Root CA 2022,O=SSL Corporation,C=US",
|
||||
sha256Hash: "8faf7d2e2cb4709bb8e0b33666bf75a5dd45b5de480f8ea8d4bfe6bebc17f2ed",
|
||||
certStartOff: 105779,
|
||||
certLength: 1421,
|
||||
},
|
||||
{
|
||||
cn: "CN=SZAFIR ROOT CA2,O=Krajowa Izba Rozliczeniowa S.A.,C=PL",
|
||||
sha256Hash: "a1339d33281a0b56e557d3d32b1ce7f9367eb094bd5fa72a7e5004c8ded7cafe",
|
||||
certStartOff: 107200,
|
||||
certLength: 886,
|
||||
},
|
||||
{
|
||||
cn: "CN=Sectigo Public Server Authentication Root E46,O=Sectigo Limited,C=GB",
|
||||
sha256Hash: "c90f26f0fb1b4018b22227519b5ca2b53e2ca5b3be5cf18efe1bef47380c5383",
|
||||
certStartOff: 108086,
|
||||
certLength: 574,
|
||||
},
|
||||
{
|
||||
cn: "CN=Sectigo Public Server Authentication Root R46,O=Sectigo Limited,C=GB",
|
||||
sha256Hash: "7bb647a62aeeac88bf257aa522d01ffea395e0ab45c73f93f65654ec38f25a06",
|
||||
certStartOff: 108660,
|
||||
certLength: 1422,
|
||||
},
|
||||
{
|
||||
cn: "CN=Secure Global CA,O=SecureTrust Corporation,C=US",
|
||||
sha256Hash: "4200f5043ac8590ebb527d209ed1503029fbcbd41ca1b506ec27f15ade7dac69",
|
||||
certStartOff: 110082,
|
||||
certLength: 960,
|
||||
},
|
||||
{
|
||||
cn: "CN=SecureSign Root CA12,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
|
||||
sha256Hash: "3f034bb5704d44b2d08545a02057de93ebf3905fce721acbc730c06ddaee904e",
|
||||
certStartOff: 111042,
|
||||
certLength: 886,
|
||||
},
|
||||
{
|
||||
cn: "CN=SecureSign Root CA14,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
|
||||
sha256Hash: "4b009c1034494f9ab56bba3ba1d62731fc4d20d8955adcec10a925607261e338",
|
||||
certStartOff: 111928,
|
||||
certLength: 1398,
|
||||
},
|
||||
{
|
||||
cn: "CN=SecureSign Root CA15,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
|
||||
sha256Hash: "e778f0f095fe843729cd1a0082179e5314a9c291442805e1fb1d8fb6b8886c3a",
|
||||
certStartOff: 113326,
|
||||
certLength: 551,
|
||||
},
|
||||
{
|
||||
cn: "CN=SecureTrust CA,O=SecureTrust Corporation,C=US",
|
||||
sha256Hash: "f1c1b50ae5a20dd8030ec9f6bc24823dd367b5255759b4e71b61fce9f7375d73",
|
||||
certStartOff: 113877,
|
||||
certLength: 956,
|
||||
},
|
||||
{
|
||||
cn: "CN=Security Communication ECC RootCA1,O=SECOM Trust Systems CO.\\,LTD.,C=JP",
|
||||
sha256Hash: "e74fbda55bd564c473a36b441aa799c8a68e077440e8288b9fa1e50e4bbaca11",
|
||||
certStartOff: 114833,
|
||||
certLength: 572,
|
||||
},
|
||||
{
|
||||
cn: "CN=Starfield Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
|
||||
sha256Hash: "2ce1cb0bf9d2f9e102993fbe215152c3b2dd0cabde1c68e5319b839154dbb7f5",
|
||||
certStartOff: 115405,
|
||||
certLength: 993,
|
||||
},
|
||||
{
|
||||
cn: "CN=Starfield Services Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
|
||||
sha256Hash: "568d6905a2c88708a4b3025190edcfedb1974a606a13c6e5290fcb2ae63edab5",
|
||||
certStartOff: 116398,
|
||||
certLength: 1011,
|
||||
},
|
||||
{
|
||||
cn: "CN=SwissSign Gold CA - G2,O=SwissSign AG,C=CH",
|
||||
sha256Hash: "62dd0be9b9f50a163ea0f8e75c053b1eca57ea55c8688f647c6881f2c8357b95",
|
||||
certStartOff: 117409,
|
||||
certLength: 1470,
|
||||
},
|
||||
{
|
||||
cn: "CN=SwissSign RSA TLS Root CA 2022 - 1,O=SwissSign AG,C=CH",
|
||||
sha256Hash: "193144f431e0fddb740717d4de926a571133884b4360d30e272913cbe660ce41",
|
||||
certStartOff: 118879,
|
||||
certLength: 1431,
|
||||
},
|
||||
{
|
||||
cn: "CN=T-TeleSec GlobalRoot Class 2,OU=T-Systems Trust Center,O=T-Systems Enterprise Services GmbH,C=DE",
|
||||
sha256Hash: "91e2f5788d5810eba7ba58737de1548a8ecacd014598bc0b143e041b17052552",
|
||||
certStartOff: 120310,
|
||||
certLength: 967,
|
||||
},
|
||||
{
|
||||
cn: "CN=T-TeleSec GlobalRoot Class 3,OU=T-Systems Trust Center,O=T-Systems Enterprise Services GmbH,C=DE",
|
||||
sha256Hash: "fd73dad31c644ff1b43bef0ccdda96710b9cd9875eca7e31707af3e96d522bbd",
|
||||
certStartOff: 121277,
|
||||
certLength: 967,
|
||||
},
|
||||
{
|
||||
cn: "CN=TWCA CYBER Root CA,OU=Root CA,O=TAIWAN-CA,C=TW",
|
||||
sha256Hash: "3f63bb2814be174ec8b6439cf08d6d56f0b7c405883a5648a334424d6b3ec558",
|
||||
certStartOff: 122244,
|
||||
certLength: 1425,
|
||||
},
|
||||
{
|
||||
cn: "CN=TWCA Global Root CA,OU=Root CA,O=TAIWAN-CA,C=TW",
|
||||
sha256Hash: "59769007f7685d0fcd50872f9f95d5755a5b2b457d81f3692b610a98672f0e1b",
|
||||
certStartOff: 123669,
|
||||
certLength: 1349,
|
||||
},
|
||||
{
|
||||
cn: "CN=TWCA Root Certification Authority,OU=Root CA,O=TAIWAN-CA,C=TW",
|
||||
sha256Hash: "bfd88fe1101c41ae3e801bf8be56350ee9bad1a6b9bd515edc5c6d5b8711ac44",
|
||||
certStartOff: 125018,
|
||||
certLength: 895,
|
||||
},
|
||||
{
|
||||
cn: "CN=Telekom Security TLS ECC Root 2020,O=Deutsche Telekom Security GmbH,C=DE",
|
||||
sha256Hash: "578af4ded0853f4e5998db4aeaf9cbea8d945f60b620a38d1a3c13b2bc7ba8e1",
|
||||
certStartOff: 125913,
|
||||
certLength: 582,
|
||||
},
|
||||
{
|
||||
cn: "CN=Telekom Security TLS RSA Root 2023,O=Deutsche Telekom Security GmbH,C=DE",
|
||||
sha256Hash: "efc65cadbb59adb6efe84da22311b35624b71b3b1ea0da8b6655174ec8978646",
|
||||
certStartOff: 126495,
|
||||
certLength: 1463,
|
||||
},
|
||||
{
|
||||
cn: "CN=Telia Root CA v2,O=Telia Finland Oyj,C=FI",
|
||||
sha256Hash: "242b69742fcb1e5b2abf98898b94572187544e5b4d9911786573621f6a74b82c",
|
||||
certStartOff: 127958,
|
||||
certLength: 1400,
|
||||
},
|
||||
{
|
||||
cn: "CN=TeliaSonera Root CA v1,O=TeliaSonera",
|
||||
sha256Hash: "dd6936fe21f8f077c123a1a521c12224f72255b73e03a7260693e8a24b0fa389",
|
||||
certStartOff: 129358,
|
||||
certLength: 1340,
|
||||
},
|
||||
{
|
||||
cn: "CN=TrustAsia Global Root CA G3,O=TrustAsia Technologies\\, Inc.,C=CN",
|
||||
sha256Hash: "e0d3226aeb1163c2e48ff9be3b50b4c6431be7bb1eacc5c36b5d5ec509039a08",
|
||||
certStartOff: 130698,
|
||||
certLength: 1449,
|
||||
},
|
||||
{
|
||||
cn: "CN=TrustAsia Global Root CA G4,O=TrustAsia Technologies\\, Inc.,C=CN",
|
||||
sha256Hash: "be4b56cb5056c0136a526df444508daa36a0b54f42e4ac38f72af470e479654c",
|
||||
certStartOff: 132147,
|
||||
certLength: 601,
|
||||
},
|
||||
{
|
||||
cn: "CN=TrustAsia TLS ECC Root CA,O=TrustAsia Technologies\\, Inc.,C=CN",
|
||||
sha256Hash: "c0076b9ef0531fb1a656d67c4ebe97cd5dbaa41ef44598acc2489878c92d8711",
|
||||
certStartOff: 132748,
|
||||
certLength: 565,
|
||||
},
|
||||
{
|
||||
cn: "CN=TrustAsia TLS RSA Root CA,O=TrustAsia Technologies\\, Inc.,C=CN",
|
||||
sha256Hash: "06c08d7dafd876971eb1124fe67f847ec0c7a158d3ea53cbe940e2ea9791f4c3",
|
||||
certStartOff: 133313,
|
||||
certLength: 1412,
|
||||
},
|
||||
{
|
||||
cn: "CN=Trustwave Global Certification Authority,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US",
|
||||
sha256Hash: "97552015f5ddfc3c8788c006944555408894450084f100867086bc1a2bb58dc8",
|
||||
certStartOff: 134725,
|
||||
certLength: 1502,
|
||||
},
|
||||
{
|
||||
cn: "CN=Trustwave Global ECC P256 Certification Authority,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US",
|
||||
sha256Hash: "945bbc825ea554f489d1fd51a73ddf2ea624ac7019a05205225c22a78ccfa8b4",
|
||||
certStartOff: 136227,
|
||||
certLength: 612,
|
||||
},
|
||||
{
|
||||
cn: "CN=Trustwave Global ECC P384 Certification Authority,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US",
|
||||
sha256Hash: "55903859c8c0c3ebb8759ece4e2557225ff5758bbd38ebd48276601e1bd58097",
|
||||
certStartOff: 136839,
|
||||
certLength: 673,
|
||||
},
|
||||
{
|
||||
cn: "CN=TunTrust Root CA,O=Agence Nationale de Certification Electronique,C=TN",
|
||||
sha256Hash: "2e44102ab58cb85419451c8e19d9acf3662cafbc614b6a53960a30f7d0e2eb41",
|
||||
certStartOff: 137512,
|
||||
certLength: 1463,
|
||||
},
|
||||
{
|
||||
cn: "CN=UCA Extended Validation Root,O=UniTrust,C=CN",
|
||||
sha256Hash: "d43af9b35473755c9684fc06d7d8cb70ee5c28e773fb294eb41ee71722924d24",
|
||||
certStartOff: 138975,
|
||||
certLength: 1374,
|
||||
},
|
||||
{
|
||||
cn: "CN=UCA Global G2 Root,O=UniTrust,C=CN",
|
||||
sha256Hash: "9bea11c976fe014764c1be56a6f914b5a560317abd9988393382e5161aa0493c",
|
||||
certStartOff: 140349,
|
||||
certLength: 1354,
|
||||
},
|
||||
{
|
||||
cn: "CN=USERTrust ECC Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US",
|
||||
sha256Hash: "4ff460d54b9c86dabfbcfc5712e0400d2bed3fbc4d4fbdaa86e06adcd2a9ad7a",
|
||||
certStartOff: 141703,
|
||||
certLength: 659,
|
||||
},
|
||||
{
|
||||
cn: "CN=USERTrust RSA Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US",
|
||||
sha256Hash: "e793c9b02fd8aa13e21c31228accb08119643b749c898964b1746d46c3d4cbd2",
|
||||
certStartOff: 142362,
|
||||
certLength: 1506,
|
||||
},
|
||||
{
|
||||
cn: "CN=e-Szigno Root CA 2017,O=Microsec Ltd.,L=Budapest,C=HU,2.5.4.97=#130e56415448552d3233353834343937",
|
||||
sha256Hash: "beb00b30839b9bc32c32e4447905950641f26421b15ed089198b518ae2ea1b99",
|
||||
certStartOff: 143868,
|
||||
certLength: 580,
|
||||
},
|
||||
{
|
||||
cn: "CN=emSign ECC Root CA - C3,OU=emSign PKI,O=eMudhra Inc,C=US",
|
||||
sha256Hash: "bc4d809b15189d78db3e1d8cf4f9726a795da1643ca5f1358e1ddb0edc0d7eb3",
|
||||
certStartOff: 144448,
|
||||
certLength: 559,
|
||||
},
|
||||
{
|
||||
cn: "CN=emSign ECC Root CA - G3,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN",
|
||||
sha256Hash: "86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b",
|
||||
certStartOff: 145007,
|
||||
certLength: 594,
|
||||
},
|
||||
{
|
||||
cn: "CN=emSign Root CA - C1,OU=emSign PKI,O=eMudhra Inc,C=US",
|
||||
sha256Hash: "125609aa301da0a249b97a8239cb6a34216f44dcac9f3954b14292f2e8c8608f",
|
||||
certStartOff: 145601,
|
||||
certLength: 887,
|
||||
},
|
||||
{
|
||||
cn: "CN=emSign Root CA - G1,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN",
|
||||
sha256Hash: "40f6af0346a99aa1cd1d555a4e9cce62c7f9634603ee406615833dc8c8d00367",
|
||||
certStartOff: 146488,
|
||||
certLength: 920,
|
||||
},
|
||||
{
|
||||
cn: "CN=vTrus ECC Root CA,O=iTrusChina Co.\\,Ltd.,C=CN",
|
||||
sha256Hash: "30fbba2c32238e2a98547af97931e550428b9b3f1c8eeb6633dcfa86c5b27dd3",
|
||||
certStartOff: 147408,
|
||||
certLength: 531,
|
||||
},
|
||||
{
|
||||
cn: "CN=vTrus Root CA,O=iTrusChina Co.\\,Ltd.,C=CN",
|
||||
sha256Hash: "8a71de6559336f426c26e53880d00d88a18da4c6a91f0dcb6194e206c5c96387",
|
||||
certStartOff: 147939,
|
||||
certLength: 1370,
|
||||
},
|
||||
{
|
||||
cn: "OU=AC RAIZ FNMT-RCM,O=FNMT-RCM,C=ES",
|
||||
sha256Hash: "ebc5570c29018c4d67b1aa127baf12f703b4611ebc17b7dab5573894179b93fa",
|
||||
certStartOff: 149309,
|
||||
certLength: 1415,
|
||||
},
|
||||
{
|
||||
cn: "OU=Security Communication RootCA2,O=SECOM Trust Systems CO.\\,LTD.,C=JP",
|
||||
sha256Hash: "513b2cecb810d4cde5dd85391adfc6c2dd60d87bb736d2b521484aa47a0ebef6",
|
||||
certStartOff: 150724,
|
||||
certLength: 891,
|
||||
},
|
||||
{
|
||||
cn: "OU=certSIGN ROOT CA G2,O=CERTSIGN SA,C=RO",
|
||||
sha256Hash: "657cfe2fa73faa38462571f332a2363a46fce7020951710702cdfbb6eeda3305",
|
||||
certStartOff: 151615,
|
||||
certLength: 1355,
|
||||
},
|
||||
{
|
||||
cn: "OU=certSIGN ROOT CA,O=certSIGN,C=RO",
|
||||
sha256Hash: "eaa962c4fa4a6bafebe415196d351ccd888d4f53f3fa8ae6d7c466a94e6042bb",
|
||||
certStartOff: 152970,
|
||||
certLength: 828,
|
||||
},
|
||||
{
|
||||
cn: "OU=ePKI Root Certification Authority,O=Chunghwa Telecom Co.\\, Ltd.,C=TW",
|
||||
sha256Hash: "c0a6f4dc63a24bfdcf54ef2a6a082a0a72de35803e2ff5ff527ae5d87206dfd5",
|
||||
certStartOff: 153798,
|
||||
certLength: 1460,
|
||||
distrustAfter: "2025-04-15T23:59:59Z",
|
||||
},
|
||||
{
|
||||
cn: "SERIALNUMBER=G63287510,CN=ANF Secure Server Root CA,OU=ANF CA Raiz,O=ANF Autoridad de Certificacion,C=ES",
|
||||
sha256Hash: "fb8fec759169b9106b1e511644c618c51304373f6c0643088d8beffd1b997599",
|
||||
certStartOff: 155258,
|
||||
certLength: 1523,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bundle
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBundle(t *testing.T) {
|
||||
for i, unparsed := range unparsedCertificates {
|
||||
cert, err := x509.ParseCertificate(rawCerts[unparsed.certStartOff : unparsed.certStartOff+unparsed.certLength])
|
||||
if err != nil {
|
||||
t.Errorf("ParseCertificate(unparsedCertificates[%v]) unexpected error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if unparsed.cn != cert.Subject.String() {
|
||||
t.Errorf("unparsedCertificates[%v].cn = %q; want = %q", i, unparsed.cn, cert.Subject.String())
|
||||
}
|
||||
|
||||
sum := sha256.Sum256(cert.Raw)
|
||||
sumHex := hex.EncodeToString(sum[:])
|
||||
if sumHex != unparsed.sha256Hash {
|
||||
t.Errorf("unparsedCertificates[%v].sha256Hash = %q; want = %q", i, unparsed.sha256Hash, sumHex)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package bundle contains the bundle of root certificates parsed from the NSS
|
||||
// trust store, using x509roots/nss.
|
||||
package bundle
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"iter"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed bundle.der
|
||||
var rawCerts []byte
|
||||
|
||||
// Root represents a root certificate parsed from the NSS trust store.
|
||||
type Root struct {
|
||||
// Certificate is the DER-encoded certificate (read-only; do not modify!).
|
||||
Certificate []byte
|
||||
|
||||
// Constraint is nil if the root is unconstrained. If Constraint is non-nil,
|
||||
// the certificate has additional constraints that cannot be encoded in
|
||||
// X.509, and when building a certificate chain anchored with this root the
|
||||
// chain should be passed to this function to check its validity. If using a
|
||||
// [crypto/x509.CertPool] the root should be added using
|
||||
// [crypto/x509.CertPool.AddCertWithConstraint].
|
||||
Constraint func([]*x509.Certificate) error
|
||||
}
|
||||
|
||||
// Roots returns the bundle of root certificates from the NSS trust store. The
|
||||
// [Root.Certificate] slice must be treated as read-only and should not be
|
||||
// modified.
|
||||
func Roots() iter.Seq[Root] {
|
||||
return func(yield func(Root) bool) {
|
||||
for _, unparsed := range unparsedCertificates {
|
||||
root := Root{
|
||||
Certificate: rawCerts[unparsed.certStartOff : unparsed.certStartOff+unparsed.certLength],
|
||||
}
|
||||
// parse possible constraints, this should check all fields of unparsedCertificate.
|
||||
if unparsed.distrustAfter != "" {
|
||||
distrustAfter, err := time.Parse(time.RFC3339, unparsed.distrustAfter)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to parse distrustAfter %q: %s", unparsed.distrustAfter, err))
|
||||
}
|
||||
root.Constraint = func(chain []*x509.Certificate) error {
|
||||
for _, c := range chain {
|
||||
if c.NotBefore.After(distrustAfter) {
|
||||
return fmt.Errorf("certificate issued after distrust-after date %q", distrustAfter)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if !yield(root) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type unparsedCertificate struct {
|
||||
cn string
|
||||
sha256Hash string
|
||||
certStartOff int
|
||||
certLength int
|
||||
|
||||
// possible constraints
|
||||
distrustAfter string
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bundle
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRootsCanBeParsed(t *testing.T) {
|
||||
for root := range Roots() {
|
||||
if _, err := x509.ParseCertificate(root.Certificate); err != nil {
|
||||
t.Fatalf("Could not parse root certificate: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,8 @@ package fallback
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/x509roots/fallback/bundle"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -31,71 +30,16 @@ func init() {
|
||||
|
||||
func newFallbackCertPool() *x509.CertPool {
|
||||
p := x509.NewCertPool()
|
||||
for _, c := range mustParse(unparsedCertificates) {
|
||||
if len(c.constraints) == 0 {
|
||||
p.AddCert(c.cert)
|
||||
for c := range bundle.Roots() {
|
||||
cert, err := x509.ParseCertificate(c.Certificate)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if c.Constraint == nil {
|
||||
p.AddCert(cert)
|
||||
} else {
|
||||
p.AddCertWithConstraint(c.cert, func(chain []*x509.Certificate) error {
|
||||
for _, constraint := range c.constraints {
|
||||
if err := constraint(chain); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
p.AddCertWithConstraint(cert, c.Constraint)
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
type unparsedCertificate struct {
|
||||
cn string
|
||||
sha256Hash string
|
||||
pem string
|
||||
|
||||
// possible constraints
|
||||
distrustAfter string
|
||||
}
|
||||
|
||||
type parsedCertificate struct {
|
||||
cert *x509.Certificate
|
||||
constraints []func([]*x509.Certificate) error
|
||||
}
|
||||
|
||||
func mustParse(unparsedCerts []unparsedCertificate) []parsedCertificate {
|
||||
var b []parsedCertificate
|
||||
for _, unparsed := range unparsedCerts {
|
||||
block, rest := pem.Decode([]byte(unparsed.pem))
|
||||
if block == nil {
|
||||
panic(fmt.Sprintf("unexpected nil PEM block for %q", unparsed.cn))
|
||||
}
|
||||
if len(rest) != 0 {
|
||||
panic(fmt.Sprintf("unexpected trailing data in PEM for %q", unparsed.cn))
|
||||
}
|
||||
if block.Type != "CERTIFICATE" {
|
||||
panic(fmt.Sprintf("unexpected PEM block type for %q: %s", unparsed.cn, block.Type))
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
parsed := parsedCertificate{cert: cert}
|
||||
// parse possible constraints, this should check all fields of unparsedCertificate.
|
||||
if unparsed.distrustAfter != "" {
|
||||
distrustAfter, err := time.Parse(time.RFC3339, unparsed.distrustAfter)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to parse distrustAfter %q: %s", unparsed.distrustAfter, err))
|
||||
}
|
||||
parsed.constraints = append(parsed.constraints, func(chain []*x509.Certificate) error {
|
||||
for _, c := range chain {
|
||||
if c.NotBefore.After(distrustAfter) {
|
||||
return fmt.Errorf("certificate issued after distrust-after date %q", distrustAfter)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
b = append(b, parsed)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module golang.org/x/crypto/x509roots/fallback
|
||||
|
||||
go 1.23.0
|
||||
go 1.24.0
|
||||
|
||||
@@ -11,7 +11,6 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
@@ -28,7 +27,7 @@ import (
|
||||
|
||||
const tmpl = `// Code generated by gen_fallback_bundle.go; DO NOT EDIT.
|
||||
|
||||
package fallback
|
||||
package bundle
|
||||
|
||||
var unparsedCertificates = []unparsedCertificate{
|
||||
`
|
||||
@@ -36,7 +35,8 @@ var unparsedCertificates = []unparsedCertificate{
|
||||
var (
|
||||
certDataURL = flag.String("certdata-url", "https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt", "URL to the raw certdata.txt file to parse (certdata-path overrides this, if provided)")
|
||||
certDataPath = flag.String("certdata-path", "", "Path to the NSS certdata.txt file to parse (this overrides certdata-url, if provided)")
|
||||
output = flag.String("output", "fallback/bundle.go", "Path to file to write output to")
|
||||
output = flag.String("output", "fallback/bundle/bundle.go", "Path to file to write output to")
|
||||
derOutput = flag.String("deroutput", "fallback/bundle/bundle.der", "Path to file to write output to (DER certificate bundle)")
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -92,8 +92,9 @@ func main() {
|
||||
return subjI < subjJ
|
||||
})
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
b.WriteString(tmpl)
|
||||
rawCertsData := new(bytes.Buffer)
|
||||
goSrcOut := new(bytes.Buffer)
|
||||
goSrcOut.WriteString(tmpl)
|
||||
for _, c := range certs {
|
||||
var constraints []string
|
||||
var skip bool
|
||||
@@ -110,19 +111,24 @@ func main() {
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(b, "{\ncn: %q,\nsha256Hash: \"%x\",\npem: `%s`,\n",
|
||||
|
||||
off := rawCertsData.Len()
|
||||
rawCertsData.Write(c.X509.Raw)
|
||||
|
||||
fmt.Fprintf(goSrcOut, "{\ncn: %q,\nsha256Hash: \"%x\",\ncertStartOff: %v,\ncertLength: %v,\n",
|
||||
c.X509.Subject.String(),
|
||||
sha256.Sum256(c.X509.Raw),
|
||||
string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c.X509.Raw})),
|
||||
off,
|
||||
len(c.X509.Raw),
|
||||
)
|
||||
for _, constraint := range constraints {
|
||||
fmt.Fprintln(b, constraint)
|
||||
fmt.Fprintln(goSrcOut, constraint)
|
||||
}
|
||||
fmt.Fprintln(b, "},")
|
||||
fmt.Fprintln(goSrcOut, "},")
|
||||
}
|
||||
fmt.Fprintln(b, "}")
|
||||
fmt.Fprintln(goSrcOut, "}")
|
||||
|
||||
formatted, err := format.Source(b.Bytes())
|
||||
formatted, err := format.Source(goSrcOut.Bytes())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to format source: %s", err)
|
||||
}
|
||||
@@ -130,4 +136,8 @@ func main() {
|
||||
if err := os.WriteFile(*output, formatted, 0644); err != nil {
|
||||
log.Fatalf("failed to write to %q: %s", *output, err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(*derOutput, rawCertsData.Bytes(), 0644); err != nil {
|
||||
log.Fatalf("failed to write to %q: %s", *output, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
//
|
||||
// Note that XTS is usually not appropriate for any use besides disk encryption.
|
||||
// Most users should use an AEAD mode like GCM (from crypto/cipher.NewGCM) instead.
|
||||
//
|
||||
// The xts package is [frozen] and is not accepting new features.
|
||||
//
|
||||
// [frozen]: https://go.dev/wiki/Frozen
|
||||
package xts
|
||||
|
||||
import (
|
||||
|
||||
Reference in New Issue
Block a user