mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-25 23:49:37 -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
|
Note over Client: Verify Attestation
|
||||||
Client->>Server: ClientKeyExchange
|
Client->>Server: ClientKeyExchange
|
||||||
Client->>Server: ChangeCipherSpec, Finished
|
Client->>Server: ChangeCipherSpec, Finished
|
||||||
Server->>Client:
|
Server->>Client: #
|
||||||
```
|
```
|
||||||
|
|
||||||
## Server side verification
|
## Server side verification
|
||||||
|
|
||||||
1. The client sends a ClientHello message
|
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.
|
* 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.
|
4. The server verifies the client's attestation statement.
|
||||||
@ -48,7 +48,7 @@ sequenceDiagram
|
|||||||
participant Client
|
participant Client
|
||||||
participant Server
|
participant Server
|
||||||
Client->>Server: ClientHello
|
Client->>Server: ClientHello
|
||||||
Server->>Client: ServerCertificate(nonce), ServerHelloDone
|
Server->>Client: ServerCertificate, AcceptableCAs(nonce), ServerHelloDone
|
||||||
Client->>Server: ClientKeyExchange, ClientCertificate(AttestationStatement)
|
Client->>Server: ClientKeyExchange, ClientCertificate(AttestationStatement)
|
||||||
Client->>Server: ChangeCipherSpec, Finished
|
Client->>Server: ChangeCipherSpec, Finished
|
||||||
Note over Server: Verify Attestation
|
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.
|
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.
|
* 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.
|
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.
|
* 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.
|
5. The server verifies the client's attestation statement.
|
||||||
@ -77,7 +77,7 @@ sequenceDiagram
|
|||||||
participant Client
|
participant Client
|
||||||
participant Server
|
participant Server
|
||||||
Client->>Server: ClientHello(nonce)
|
Client->>Server: ClientHello(nonce)
|
||||||
Server->>Client: ServerCertificate(AttestationStatement, nonce), ServerHelloDone
|
Server->>Client: ServerCertificate(AttestationStatement), AcceptableCAs(nonce), ServerHelloDone
|
||||||
Note over Client: Verify Attestation
|
Note over Client: Verify Attestation
|
||||||
Client->>Server: ClientKeyExchange, ClientCertificate(AttestationStatement)
|
Client->>Server: ClientKeyExchange, ClientCertificate(AttestationStatement)
|
||||||
Client->>Server: ChangeCipherSpec, Finished
|
Client->>Server: ChangeCipherSpec, Finished
|
||||||
|
@ -9,8 +9,11 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/coordinator/config"
|
"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.
|
// 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 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.
|
// If issuer is nil, the client will be unable to perform mutual aTLS.
|
||||||
func CreateAttestationClientTLSConfig(issuer Issuer, validators []Validator) (*tls.Config, error) {
|
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.
|
// 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.
|
// 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) {
|
func getATLSConfigForClientFunc(issuer Issuer, validators []Validator) (func(*tls.ClientHelloInfo) (*tls.Config, error), error) {
|
||||||
// generate key for the server
|
// generate key for the server
|
||||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
@ -90,25 +98,29 @@ func getATLSConfigForClientFunc(issuer Issuer, validators []Validator) (func(*tl
|
|||||||
serverNonce: serverNonce,
|
serverNonce: serverNonce,
|
||||||
}
|
}
|
||||||
|
|
||||||
clientAuth := tls.NoClientCert
|
cfg := &tls.Config{
|
||||||
// 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,
|
|
||||||
VerifyPeerCertificate: serverConn.verify,
|
VerifyPeerCertificate: serverConn.verify,
|
||||||
GetCertificate: serverConn.getCertificate,
|
GetCertificate: serverConn.getCertificate,
|
||||||
MinVersion: tls.VersionTLS12,
|
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
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCertificate creates a client or server certificate for aTLS connections.
|
// getCertificate creates a client or server certificate for aTLS connections.
|
||||||
// The certificate uses certificate extensions to embed an attestation document generated using remoteNonce.
|
// The certificate uses certificate extensions to embed an attestation document generated using nonce.
|
||||||
// If localNonce is set, it is also embedded as a certificate extension.
|
func getCertificate(issuer Issuer, priv, pub any, nonce []byte) (*tls.Certificate, error) {
|
||||||
func getCertificate(issuer Issuer, priv, pub any, remoteNonce, localNonce []byte) (*tls.Certificate, error) {
|
|
||||||
serialNumber, err := util.GenerateCertificateSerialNumber()
|
serialNumber, err := util.GenerateCertificateSerialNumber()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
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})
|
extensions = append(extensions, pkix.Extension{Id: issuer.OID(), Value: attDoc})
|
||||||
}
|
}
|
||||||
|
|
||||||
// embed locally generated nonce in certificate
|
// create certificate that includes the attestation document as extension
|
||||||
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
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
template := &x509.Certificate{
|
template := &x509.Certificate{
|
||||||
SerialNumber: serialNumber,
|
SerialNumber: serialNumber,
|
||||||
@ -208,12 +215,59 @@ func hashPublicKey(pub any) ([]byte, error) {
|
|||||||
return result[:], nil
|
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.
|
// clientConnection holds state for client to server connections.
|
||||||
type clientConnection struct {
|
type clientConnection struct {
|
||||||
issuer Issuer
|
issuer Issuer
|
||||||
validators []Validator
|
validators []Validator
|
||||||
clientNonce []byte
|
clientNonce []byte
|
||||||
serverNonce []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify the validity of an aTLS server certificate.
|
// verify the validity of an aTLS server certificate.
|
||||||
@ -223,13 +277,6 @@ func (c *clientConnection) verify(rawCerts [][]byte, verifiedChains [][]*x509.Ce
|
|||||||
return err
|
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
|
// don't perform verification of attestation document if no validators are set
|
||||||
if len(c.validators) == 0 {
|
if len(c.validators) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@ -239,18 +286,20 @@ func (c *clientConnection) verify(rawCerts [][]byte, verifiedChains [][]*x509.Ce
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getCertificate generates a client certificate for mutual aTLS connections.
|
// 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
|
// generate and hash key
|
||||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create aTLS certificate using the server's nonce as read by clientConnection.verify
|
// ugly hack: abuse acceptable client CAs as a channel to receive the nonce
|
||||||
// we do not pass a nonce because
|
serverNonce, err := decodeNonceFromAcceptableCAs(cri.AcceptableCAs)
|
||||||
// 1. we already received a certificate from the server
|
if err != nil {
|
||||||
// 2. we transmitted the client nonce as our server name in our client-hello message
|
return nil, fmt.Errorf("decode nonce: %w", err)
|
||||||
return getCertificate(c.issuer, priv, &priv.PublicKey, c.serverNonce, nil)
|
}
|
||||||
|
|
||||||
|
return getCertificate(c.issuer, priv, &priv.PublicKey, serverNonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
// serverConnection holds state for server to client connections.
|
// 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
|
// 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)
|
||||||
return getCertificate(c.issuer, c.privKey, &c.privKey.PublicKey, clientNonce, c.serverNonce)
|
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,6 @@ import (
|
|||||||
"encoding/asn1"
|
"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.
|
// Getter returns an ASN.1 Object Identifier.
|
||||||
type Getter interface {
|
type Getter interface {
|
||||||
OID() asn1.ObjectIdentifier
|
OID() asn1.ObjectIdentifier
|
||||||
|
Loading…
Reference in New Issue
Block a user