mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-25 07:29:38 -05:00
atls: make client cfg reusable
This commit is contained in:
parent
989c128fa6
commit
e9916a7d3a
@ -27,16 +27,16 @@ sequenceDiagram
|
||||
Note over Client: Verify Attestation
|
||||
Client->>Server: ClientKeyExchange
|
||||
Client->>Server: ChangeCipherSpec, Finished
|
||||
Server->>Client:
|
||||
Server->>Client: #
|
||||
```
|
||||
|
||||
## Server side verification
|
||||
|
||||
1. The client sends a ClientHello message
|
||||
|
||||
2. The server sends back a certificate with a random nonce. The nonce is embedded using x509 certificate extensions with the OID `1.3.9900.0.1`.
|
||||
2. The server sends back a certificate and a random nonce. The nonce is encoded as the Distinguished Name of an acceptable CA.
|
||||
|
||||
3. The client does not verify the servers certificate, but uses the embedded nonce to generate an attestation based on its CC capabilities.
|
||||
3. The client does not verify the servers certificate, but uses the nonce to generate an attestation based on its CC capabilities.
|
||||
* The attestation is embedded in the client certificate using x509 certificate extensions with an OID to identify the CC attestation type.
|
||||
|
||||
4. The server verifies the client's attestation statement.
|
||||
@ -48,7 +48,7 @@ sequenceDiagram
|
||||
participant Client
|
||||
participant Server
|
||||
Client->>Server: ClientHello
|
||||
Server->>Client: ServerCertificate(nonce), ServerHelloDone
|
||||
Server->>Client: ServerCertificate, AcceptableCAs(nonce), ServerHelloDone
|
||||
Client->>Server: ClientKeyExchange, ClientCertificate(AttestationStatement)
|
||||
Client->>Server: ChangeCipherSpec, Finished
|
||||
Note over Server: Verify Attestation
|
||||
@ -61,11 +61,11 @@ sequenceDiagram
|
||||
|
||||
2. The server generates an attestation statement using the clients nonce and its CC capabilities.
|
||||
* The attestation is embedded in the server certificate using x509 certificate extensions with an OID to identify the attestation type.
|
||||
* A nonce is embedded using x509 certificate extensions with the OID `1.3.9900.0.1`.
|
||||
* A nonce is encoded as the Distinguished Name of an acceptable CA.
|
||||
|
||||
3. The client verifies the attestation statement.
|
||||
|
||||
4. The client uses the nonce embedded in the server's certificate to generate an attestation based on its CC capabilities.
|
||||
4. The client uses the nonce to generate an attestation based on its CC capabilities.
|
||||
* The attestation is embedded in the client certificate using x509 certificate extensions with an OID to identify the CC attestation type.
|
||||
|
||||
5. The server verifies the client's attestation statement.
|
||||
@ -77,7 +77,7 @@ sequenceDiagram
|
||||
participant Client
|
||||
participant Server
|
||||
Client->>Server: ClientHello(nonce)
|
||||
Server->>Client: ServerCertificate(AttestationStatement, nonce), ServerHelloDone
|
||||
Server->>Client: ServerCertificate(AttestationStatement), AcceptableCAs(nonce), ServerHelloDone
|
||||
Note over Client: Verify Attestation
|
||||
Client->>Server: ClientKeyExchange, ClientCertificate(AttestationStatement)
|
||||
Client->>Server: ChangeCipherSpec, Finished
|
||||
|
@ -9,8 +9,11 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/coordinator/config"
|
||||
@ -33,6 +36,11 @@ func CreateAttestationServerTLSConfig(issuer Issuer, validators []Validator) (*t
|
||||
}
|
||||
|
||||
// CreateAttestationClientTLSConfig creates a tls.Config object that verifies a certificate with an embedded attestation document.
|
||||
//
|
||||
// ATTENTION: The tls.Config ensures freshness of the server's attestation only for the first connection it is used for.
|
||||
// If freshness is required, you must create a new tls.Config for each connection or ensure freshness on the protocol level.
|
||||
// If freshness is not required, you can reuse this tls.Config.
|
||||
//
|
||||
// If no validators are set, the server's attestation document will not be verified.
|
||||
// If issuer is nil, the client will be unable to perform mutual aTLS.
|
||||
func CreateAttestationClientTLSConfig(issuer Issuer, validators []Validator) (*tls.Config, error) {
|
||||
@ -67,7 +75,7 @@ type Validator interface {
|
||||
|
||||
// getATLSConfigForClientFunc returns a config setup function that is called once for every client connecting to the server.
|
||||
// This allows for different server configuration for every client.
|
||||
// In aTLS this is used to generate unique nonces for every client and embed them in the server's certificate.
|
||||
// In aTLS this is used to generate unique nonces for every client.
|
||||
func getATLSConfigForClientFunc(issuer Issuer, validators []Validator) (func(*tls.ClientHelloInfo) (*tls.Config, error), error) {
|
||||
// generate key for the server
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
@ -90,25 +98,29 @@ func getATLSConfigForClientFunc(issuer Issuer, validators []Validator) (func(*tl
|
||||
serverNonce: serverNonce,
|
||||
}
|
||||
|
||||
clientAuth := tls.NoClientCert
|
||||
// enable mutual aTLS if any validators are set
|
||||
if len(validators) > 0 {
|
||||
clientAuth = tls.RequireAnyClientCert // validity of certificate will be checked by our custom verify function
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
ClientAuth: clientAuth,
|
||||
cfg := &tls.Config{
|
||||
VerifyPeerCertificate: serverConn.verify,
|
||||
GetCertificate: serverConn.getCertificate,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// enable mutual aTLS if any validators are set
|
||||
if len(validators) > 0 {
|
||||
cfg.ClientAuth = tls.RequireAnyClientCert // validity of certificate will be checked by our custom verify function
|
||||
|
||||
// ugly hack: abuse acceptable client CAs as a channel to transmit the nonce
|
||||
if cfg.ClientCAs, err = encodeNonceToCertPool(serverNonce, priv); err != nil {
|
||||
return nil, fmt.Errorf("encode nonce: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getCertificate creates a client or server certificate for aTLS connections.
|
||||
// The certificate uses certificate extensions to embed an attestation document generated using remoteNonce.
|
||||
// If localNonce is set, it is also embedded as a certificate extension.
|
||||
func getCertificate(issuer Issuer, priv, pub any, remoteNonce, localNonce []byte) (*tls.Certificate, error) {
|
||||
// The certificate uses certificate extensions to embed an attestation document generated using nonce.
|
||||
func getCertificate(issuer Issuer, priv, pub any, nonce []byte) (*tls.Certificate, error) {
|
||||
serialNumber, err := util.GenerateCertificateSerialNumber()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -124,7 +136,7 @@ func getCertificate(issuer Issuer, priv, pub any, remoteNonce, localNonce []byte
|
||||
}
|
||||
|
||||
// create attestation document using the nonce send by the remote party
|
||||
attDoc, err := issuer.Issue(hash, remoteNonce)
|
||||
attDoc, err := issuer.Issue(hash, nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -132,12 +144,7 @@ func getCertificate(issuer Issuer, priv, pub any, remoteNonce, localNonce []byte
|
||||
extensions = append(extensions, pkix.Extension{Id: issuer.OID(), Value: attDoc})
|
||||
}
|
||||
|
||||
// embed locally generated nonce in certificate
|
||||
if len(localNonce) > 0 {
|
||||
extensions = append(extensions, pkix.Extension{Id: oid.ATLSNonce, Value: localNonce})
|
||||
}
|
||||
|
||||
// create certificate that includes the attestation document and the server nonce as extension
|
||||
// create certificate that includes the attestation document as extension
|
||||
now := time.Now()
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
@ -208,12 +215,59 @@ func hashPublicKey(pub any) ([]byte, error) {
|
||||
return result[:], nil
|
||||
}
|
||||
|
||||
// encodeNonceToCertPool returns a cert pool that contains a certificate whose CN is the base64-encoded nonce.
|
||||
func encodeNonceToCertPool(nonce []byte, privKey *ecdsa.PrivateKey) (*x509.CertPool, error) {
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: &big.Int{},
|
||||
Subject: pkix.Name{CommonName: base64.StdEncoding.EncodeToString(nonce)},
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, template, template, &privKey.PublicKey, privKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err := x509.ParseCertificate(der)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
pool.AddCert(cert)
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// decodeNonceFromAcceptableCAs interprets the CN of acceptableCAs[0] as base64-encoded nonce and returns the decoded nonce.
|
||||
// acceptableCAs should have been received by a client where the server used encodeNonceToCertPool to transmit the nonce.
|
||||
func decodeNonceFromAcceptableCAs(acceptableCAs [][]byte) ([]byte, error) {
|
||||
if len(acceptableCAs) != 1 {
|
||||
return nil, errors.New("unexpected acceptableCAs length")
|
||||
}
|
||||
var rdnSeq pkix.RDNSequence
|
||||
if _, err := asn1.Unmarshal(acceptableCAs[0], &rdnSeq); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// https://github.com/golang/go/blob/19309779ac5e2f5a2fd3cbb34421dafb2855ac21/src/crypto/x509/pkix/pkix.go#L188
|
||||
oidCommonName := asn1.ObjectIdentifier{2, 5, 4, 3}
|
||||
|
||||
for _, rdnSet := range rdnSeq {
|
||||
for _, rdn := range rdnSet {
|
||||
if rdn.Type.Equal(oidCommonName) {
|
||||
nonce, ok := rdn.Value.(string)
|
||||
if !ok {
|
||||
return nil, errors.New("unexpected RDN type")
|
||||
}
|
||||
return base64.StdEncoding.DecodeString(nonce)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("CN not found")
|
||||
}
|
||||
|
||||
// clientConnection holds state for client to server connections.
|
||||
type clientConnection struct {
|
||||
issuer Issuer
|
||||
validators []Validator
|
||||
clientNonce []byte
|
||||
serverNonce []byte
|
||||
}
|
||||
|
||||
// verify the validity of an aTLS server certificate.
|
||||
@ -223,13 +277,6 @@ func (c *clientConnection) verify(rawCerts [][]byte, verifiedChains [][]*x509.Ce
|
||||
return err
|
||||
}
|
||||
|
||||
// get nonce send by server from cert extensions and save to connection state
|
||||
for _, ex := range cert.Extensions {
|
||||
if ex.Id.Equal(oid.ATLSNonce) {
|
||||
c.serverNonce = ex.Value
|
||||
}
|
||||
}
|
||||
|
||||
// don't perform verification of attestation document if no validators are set
|
||||
if len(c.validators) == 0 {
|
||||
return nil
|
||||
@ -239,18 +286,20 @@ func (c *clientConnection) verify(rawCerts [][]byte, verifiedChains [][]*x509.Ce
|
||||
}
|
||||
|
||||
// getCertificate generates a client certificate for mutual aTLS connections.
|
||||
func (c *clientConnection) getCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
func (c *clientConnection) getCertificate(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
// generate and hash key
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create aTLS certificate using the server's nonce as read by clientConnection.verify
|
||||
// we do not pass a nonce because
|
||||
// 1. we already received a certificate from the server
|
||||
// 2. we transmitted the client nonce as our server name in our client-hello message
|
||||
return getCertificate(c.issuer, priv, &priv.PublicKey, c.serverNonce, nil)
|
||||
// ugly hack: abuse acceptable client CAs as a channel to receive the nonce
|
||||
serverNonce, err := decodeNonceFromAcceptableCAs(cri.AcceptableCAs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode nonce: %w", err)
|
||||
}
|
||||
|
||||
return getCertificate(c.issuer, priv, &priv.PublicKey, serverNonce)
|
||||
}
|
||||
|
||||
// serverConnection holds state for server to client connections.
|
||||
@ -282,6 +331,5 @@ func (c *serverConnection) getCertificate(chi *tls.ClientHelloInfo) (*tls.Certif
|
||||
}
|
||||
|
||||
// create aTLS certificate using the nonce as extracted from the client-hello message
|
||||
// we also embed the nonce generated for this connection in case of mutual aTLS
|
||||
return getCertificate(c.issuer, c.privKey, &c.privKey.PublicKey, clientNonce, c.serverNonce)
|
||||
return getCertificate(c.issuer, c.privKey, &c.privKey.PublicKey, clientNonce)
|
||||
}
|
||||
|
@ -4,9 +4,6 @@ import (
|
||||
"encoding/asn1"
|
||||
)
|
||||
|
||||
// ATLSNonce is the ASN.1 object identifier used to transmit a nonce from server to client.
|
||||
var ATLSNonce = asn1.ObjectIdentifier{1, 3, 9900, 0, 1}
|
||||
|
||||
// Getter returns an ASN.1 Object Identifier.
|
||||
type Getter interface {
|
||||
OID() asn1.ObjectIdentifier
|
||||
|
Loading…
Reference in New Issue
Block a user