-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add DID <> TLS integration example #516
Open
andresuribe87
wants to merge
3
commits into
TBD54566975:main
Choose a base branch
from
andresuribe87:tls_example
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# Secure TLS Communication with Self-Signed Certificates and DID Integration | ||
|
||
## Overview | ||
|
||
This Go application showcases secure communication over a network using TLS (Transport Layer Security) with dynamically generated self-signed X.509 certificates. It integrates Decentralized Identifiers (DIDs) for unique identity representation, leveraging the SSI (Self-Sovereign Identity) SDK for cryptographic operations and DID resolution. The application consists of a TLS server and a TLS client, demonstrating encrypted message exchange. | ||
|
||
|
||
## TL;DR | ||
|
||
Run this example by doing `go run main.go` from this folder. The expected result is below: | ||
``` | ||
$ go run main.go | ||
Server: Listening on localhost:8443 | ||
Client: Verifying peer certificate | ||
Server: Received 'Hello from Client' | ||
Client: Received 'Hello from Server' | ||
``` | ||
|
||
## Key Features | ||
|
||
- **TLS Server and Client Implementation**: Establishes a simple TLS server and client that securely exchange messages over the network. | ||
- **Dynamic Self-Signed Certificate Generation**: Automatically generates RSA private keys and the corresponding self-signed X.509 certificates, incorporating DIDs into the certificates' subject fields. | ||
- **DID-Based Identity**: Utilizes DIDs to uniquely identify server and client entities, converting RSA public keys into JWK (JSON Web Key) format and further into DID-based JWKs for enhanced identity management. | ||
- **Encrypted Communication**: Ensures all communications between the server and client are encrypted and authenticated through TLS, offering privacy and data integrity. | ||
|
||
> [!NOTE] | ||
> Private keys can easily be switched to any supported key type. | ||
|
||
> [!NOTE] | ||
> Other did methods besides JWK can be easily supported by adding additional resolvers. | ||
|
||
## Functionality | ||
|
||
### Initialization | ||
|
||
- Upon execution, the application initializes both the TLS server and client in separate goroutines, enabling concurrent operation. | ||
- The server listens on `localhost:8443`, ready to accept client connections. | ||
|
||
### RSA Keys and Certificate Generation | ||
|
||
- Both server and client generate their RSA private keys and use them to create self-signed certificates. The certificates embed DIDs in their common names, facilitating secure TLS connections with identity verification. | ||
|
||
### Conversion to JWK and DID | ||
|
||
- The application converts public RSA keys to JWK format, subsequently transforming them into DID-based JWKs using functionalities provided by the `ssi-sdk`. | ||
- The conversion process links TLS identities (as represented by the certificates) with decentralized identities (DIDs), showcasing a modern approach to identity management. | ||
|
||
### Certificate Verification | ||
|
||
During the TLS handshake, the client verifies the server's certificate against a list of trusted Certificate Authorities (CAs). Since the application uses self-signed certificates, the following custom verification logic is applied: | ||
|
||
- **InsecureSkipVerify**: Set to `true` to bypass the default certificate verification process, and overrides `VerifyPeerCertificate` as shown below. | ||
- **VerifyPeerCertificate**: A custom callback function provided to the TLS client's configuration. It implements additional verification checks on the peer's certificate. | ||
|
||
### Custom Verification Logic | ||
|
||
The custom verification logic includes: | ||
|
||
1. **Certificate Parsing**: Parses the peer's certificate to extract critical information, including the subject's Common Name (CN), which contains the DID. | ||
1. **DID Resolution**: Utilizes the SSI SDK to resolve the DID embedded in the certificate's CN to a public key. This step simulates the process of fetching the corresponding public key from a decentralized identity document. | ||
1. **Public Key Matching**: Compares the resolved public key against the public key embedded in the certificate to ensure they match. This confirms that the certificate is indeed associated with the DID it claims to represent. | ||
1. **Integrity Check**: Verifies that the certificate's signature is valid and that it has not been tampered with. This ensures the integrity of the certificate and the authenticity of its issuer (in this case, self-issued). | ||
|
||
|
||
### Secure Message Exchange | ||
|
||
- Following the establishment of a secure TLS channel, the client sends a greeting message to the server, which responds accordingly. | ||
- The exchange demonstrates the application's capability to facilitate secure, encrypted communications over TLS. | ||
|
||
## Dependencies | ||
|
||
- **Go Standard Libraries (`crypto/x509`, `crypto/tls`, etc.)**: Used for cryptographic functions and TLS communication. | ||
- **SSI SDK (`github.com/TBD54566975/ssi-sdk/crypto/jwx`, `github.com/TBD54566975/ssi-sdk/did/jwk`, etc.)**: Utilized for DID to JWK conversion and cryptographic operations. | ||
|
||
## Getting Started | ||
|
||
1. Ensure Go is installed on your system and your GOPATH is correctly set up. | ||
2. Clone the repository and navigate to the project directory. | ||
3. Run `go run main.go` to start the server and client. | ||
4. Observe the encrypted message exchange between the server and client in the terminal. | ||
|
||
## Security Considerations | ||
|
||
This example uses self-signed certificates for simplicity and demonstration purposes. In a production environment, certificates should be obtained from a trusted Certificate Authority (CA) to ensure widespread trust and compatibility. The application exemplifies the integration of TLS security mechanisms with DIDs, suitable for secure communication in various applications, including decentralized systems and IoT devices. | ||
|
||
For feedback and contributions, please open an issue or submit a pull request. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
gocrypto "crypto" | ||
"crypto/rand" | ||
"crypto/tls" | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/pem" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"log" | ||
"math/big" | ||
"sync" | ||
"time" | ||
|
||
"github.com/TBD54566975/ssi-sdk/crypto" | ||
"github.com/TBD54566975/ssi-sdk/crypto/jwx" | ||
"github.com/TBD54566975/ssi-sdk/did/jwk" | ||
"github.com/TBD54566975/ssi-sdk/did/resolution" | ||
) | ||
|
||
const ( | ||
addr = "localhost:8443" | ||
) | ||
|
||
func main() { | ||
var wg sync.WaitGroup | ||
|
||
// Start TLS server | ||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
startTLSServer() | ||
}() | ||
|
||
// Give the server a moment to start | ||
wg.Add(1) | ||
go func() { | ||
defer wg.Done() | ||
startTLSClient() | ||
}() | ||
|
||
wg.Wait() | ||
} | ||
|
||
func startTLSServer() { | ||
_, privateKey, err := crypto.GenerateRSA2048Key() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
publicJwk, err := jwx.PublicKeyToPublicKeyJWK(nil, privateKey.Public()) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
didJwk, err := jwk.CreateDIDJWK(*publicJwk) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Define the subject details for the certificate. | ||
subject := pkix.Name{ | ||
SerialNumber: "1234", | ||
CommonName: didJwk.String(), | ||
} | ||
certPem, err := GenerateSelfSignedCert(&privateKey, privateKey.Public(), subject) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// Marshal the private key to its ASN.1 PKCS#1 DER encoded form | ||
privateKeyBytes := x509.MarshalPKCS1PrivateKey(&privateKey) | ||
|
||
// Create a PEM block with the private key | ||
privateKeyPEM := pem.EncodeToMemory(&pem.Block{ | ||
Type: "RSA PRIVATE KEY", | ||
Bytes: privateKeyBytes, | ||
}) | ||
|
||
cer, err := tls.X509KeyPair(certPem, privateKeyPEM) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
config := &tls.Config{Certificates: []tls.Certificate{cer}} | ||
ln, err := tls.Listen("tcp", addr, config) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer ln.Close() | ||
|
||
fmt.Println("Server: Listening on " + addr) | ||
conn, err := ln.Accept() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer conn.Close() | ||
|
||
buffer := make([]byte, 1024) | ||
n, err := conn.Read(buffer) | ||
if err != nil { | ||
if err != io.EOF { | ||
log.Fatal(err) | ||
} | ||
} | ||
fmt.Printf("Server: Received '%s'\n", string(buffer[:n])) | ||
_, err = conn.Write([]byte("Hello from Server")) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
func startTLSClient() { | ||
// Give the server time to start | ||
time.Sleep(1 * time.Second) | ||
|
||
_, privateKey, err := crypto.GenerateRSA2048Key() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
publicJwk, err := jwx.PublicKeyToPublicKeyJWK(nil, privateKey.Public()) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
didJwk, err := jwk.CreateDIDJWK(*publicJwk) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Define the subject details for the certificate. | ||
subject := pkix.Name{ | ||
SerialNumber: "5678", | ||
CommonName: didJwk.String(), | ||
} | ||
certPem, err := GenerateSelfSignedCert(&privateKey, privateKey.Public(), subject) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// Marshal the private key to its ASN.1 PKCS#1 DER encoded form | ||
privateKeyBytes := x509.MarshalPKCS1PrivateKey(&privateKey) | ||
|
||
// Create a PEM block with the private key | ||
privateKeyPEM := pem.EncodeToMemory(&pem.Block{ | ||
Type: "RSA PRIVATE KEY", | ||
Bytes: privateKeyBytes, | ||
}) | ||
|
||
cert, err := tls.X509KeyPair(certPem, privateKeyPEM) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
config := &tls.Config{ | ||
Certificates: []tls.Certificate{cert}, | ||
InsecureSkipVerify: true, // Only use this for testing with self-signed certs! | ||
VerifyPeerCertificate: verifyPeerCertificate, | ||
} | ||
|
||
conn, err := tls.Dial("tcp", addr, config) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer conn.Close() | ||
|
||
_, err = conn.Write([]byte("Hello from Client")) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
buffer := make([]byte, 1024) | ||
n, err := conn.Read(buffer) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
fmt.Printf("Client: Received '%s'\n", string(buffer[:n])) | ||
} | ||
|
||
func verifyPeerCertificate(certs [][]byte, chains [][]*x509.Certificate) error { | ||
fmt.Println("Client: Verifying peer certificate") | ||
|
||
if chains != nil { | ||
return errors.New("verifying peer certificate: chains expected to be nil") | ||
} | ||
|
||
cert, err := x509.ParseCertificate(certs[0]) | ||
if err != nil { | ||
return errors.New("parsing certificate") | ||
} | ||
|
||
opts := x509.VerifyOptions{ | ||
Roots: x509.NewCertPool(), | ||
} | ||
opts.Roots.AddCert(cert) | ||
|
||
_, err = cert.Verify(opts) | ||
if err != nil { | ||
return errors.New("verifying peer: " + err.Error()) | ||
} | ||
|
||
if cert.Subject.CommonName != cert.Issuer.CommonName { | ||
return errors.New("common name of subject and issuer aren't equal") | ||
} | ||
|
||
fromDid, err := certPublicKeyFromDid(cert.Subject.CommonName, jwk.Resolver{}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
jwkFromCert, err := jwx.PublicKeyToPublicKeyJWK(nil, cert.PublicKey) | ||
if err != nil { | ||
return err | ||
} | ||
if *jwkFromCert == *fromDid { | ||
return nil | ||
} | ||
|
||
return errors.New("verifying peer certificate failed") | ||
} | ||
|
||
func certPublicKeyFromDid(did string, resolver resolution.Resolver) (*jwx.PublicKeyJWK, error) { | ||
// resolve the did | ||
result, err := resolver.Resolve(context.Background(), did) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Assume there is a verification method, and use that to get the public key of the cert. | ||
return result.Document.VerificationMethod[0].PublicKeyJWK, nil | ||
} | ||
|
||
// GenerateSelfSignedCert generates a self-signed X.509 certificate for a given RSA private key and subject details. | ||
// Returns the certificate and any error encountered. | ||
func GenerateSelfSignedCert(privateKey gocrypto.PrivateKey, publicKey gocrypto.PublicKey, subject pkix.Name) ([]byte, error) { | ||
// Set certificate's serial number to a random big integer. | ||
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Prepare certificate template. | ||
certTemplate := x509.Certificate{ | ||
SerialNumber: serialNumber, | ||
Subject: subject, | ||
NotBefore: time.Now(), | ||
NotAfter: time.Now().Add(365 * 24 * time.Hour), // 1 year validity | ||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, | ||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | ||
BasicConstraintsValid: true, | ||
IsCA: true, | ||
} | ||
|
||
// Create the self-signed certificate. | ||
certBytes, err := x509.CreateCertificate(rand.Reader, &certTemplate, &certTemplate, publicKey, privateKey) | ||
if err != nil { | ||
return nil, errors.New("creating certificate: " + err.Error()) | ||
} | ||
|
||
// Encode the certificate into PEM format. | ||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) | ||
|
||
return certPEM, nil | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check failure
Code scanning / CodeQL
Disabled TLS certificate check High