Protocols
Lamassu supports a set of standards to perform some of its key functionalities such as enrolling devices as well as validating the status of a given certificate. This section aims to describe those protocols as well as explaining how them with practical examples.
OCSP
The Online Certificate Status Protocol or OCSP for short, is a protocol used to determine the current status of a digital certificate without requiring the use of Certificate Revocation Lists (CRLs).
As defined by the standard, there are two possible methods that can be used to perform the http request:
| Method | Path | Headers | Body payload | Used when | | ------ |
---------------------------------------------------------------------------------
| --------------------------------------- |
--------------------------------------------------- |
----------------------------------------------------------- | | GET
|
{url}/{url-encoding of base-64 encoding of the DER encoding of the OCSPRequest}
| | | Recommended when the encoded request is
less than 255 bytes | | PUT
| {url}
| Content-Type:
application/ocsp-request
| Binary value of the DER encoding of the OCSPRequest
| Can always be used |
GET Request
Define the OCSP server endpoint as well as the
```
export OCSP_SERVER=dev.lamassu.io:443
export CA_CERTIFICATE=issuer_ca.crt
export DEVICE_CERTIFICATE=device.crt
```
Obtain the Root certificate used by the server
```
openssl s_client -connect $OCSP_SERVER 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > root-ca.pem
```
Create the OCSP Request
```
OCSP_REQUEST=$(openssl ocsp -CAfile $CA_CERTIFICATE -issuer $CA_CERTIFICATE -cert $DEVICE_CERTIFICATE -reqout - | base64 -w 0)
```
Check the status of the certificate
```
curl --location --request GET "https://$OCSP_SERVER/api/ocsp/$OCSP_REQUEST" > ocspresponse.der
openssl ocsp -respin ocspresponse.der -VAfile root-ca.pem -resp_text
```
=== "Go" ```go package main
import (
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"io/ioutil"
"net/http"
"os"
"github.com/lamassuiot/lamassuiot/pkg/ocsp/server/crypto/ocsp"
)
func main() {
ocspServer := "http://localhost:9098"
issuerCA := "ca.crt"
certificateToCheck := "device.crt"
caPEM, err := ioutil.ReadFile(issuerCA)
if err != nil {
fmt.Println("Could not load CA certificate")
os.Exit(1)
}
caPemBlock, _ := pem.Decode(caPEM)
ca, err := x509.ParseCertificate(caPemBlock.Bytes)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
devicePEM, err := ioutil.ReadFile(certificateToCheck)
if err != nil {
fmt.Println("Could not load Device certificate")
os.Exit(1)
}
devicePemBlock, _ := pem.Decode(devicePEM)
device, err := x509.ParseCertificate(devicePemBlock.Bytes)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
ocspRequestBytes, err := ocsp.CreateRequest(device, ca, &ocsp.RequestOptions{})
if err != nil {
fmt.Println(err)
os.Exit(1)
}
encodedRequest := base64.StdEncoding.EncodeToString(ocspRequestBytes)
fmt.Println(encodedRequest)
reqURL := ocspServer + "/" + encodedRequest
resp, err := http.Get(reqURL)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if resp.StatusCode != http.StatusOK {
os.Exit(1)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
os.Exit(1)
}
resp.Body.Close()
ocspResponse, err := ocsp.ParseResponse(body, nil)
if err != nil {
fmt.Println("Could not parse OCSP response ", err)
os.Exit(1)
}
fmt.Println(ocspResponse.Status == ocsp.Good)
fmt.Println(ocspResponse.Status == ocsp.Revoked)
fmt.Println(ocspResponse.RevokedAt)
}
```
POST Request
Define the OCSP server endpoint as well as the
```
export OCSP_SERVER=dev.lamassu.io:443
export CA_CERTIFICATE=issuer_ca.crt
export DEVICE_CERTIFICATE=device.crt
```
Obtain the Root certificate used by the server
```
openssl s_client -connect $OCSP_SERVER 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > root-ca.pem
```
Create the OCSP Request
```
openssl ocsp -issuer ca.crt -cert dev.crt -reqout - > ocsp-request-post.der
```
Check the status of the certificate
```
curl --location --request POST "https://$DOMAIN/api/ocsp/" --header 'Content-Type: application/ocsp-request' --data-binary '@ocsp-request-post.der' > ocsp-response-post.der -k
openssl ocsp -respin ocsp-response-post.der -VAfile root-ca.pem -resp_text
```
=== "Go" ```go package main
import (
"bytes"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"net/http"
"os"
"github.com/lamassuiot/lamassuiot/pkg/ocsp/server/crypto/ocsp"
)
func main() {
ocspServer := "http://localhost:9098"
issuerCA := "ca.crt"
certificateToCheck := "device.crt"
caPEM, err := ioutil.ReadFile(issuerCA)
if err != nil {
fmt.Println("Could not load CA certificate")
os.Exit(1)
}
caPemBlock, _ := pem.Decode(caPEM)
ca, err := x509.ParseCertificate(caPemBlock.Bytes)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
devicePEM, err := ioutil.ReadFile(certificateToCheck)
if err != nil {
fmt.Println("Could not load Device certificate")
os.Exit(1)
}
devicePemBlock, _ := pem.Decode(devicePEM)
device, err := x509.ParseCertificate(devicePemBlock.Bytes)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
ocspRequestBytes, err := ocsp.CreateRequest(device, ca, &ocsp.RequestOptions{})
if err != nil {
fmt.Println(err)
os.Exit(1)
}
resp, err := http.Post(ocspServer, "application/ocsp-request", bytes.NewReader(ocspRequestBytes))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if resp.StatusCode != http.StatusOK {
os.Exit(1)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
os.Exit(1)
}
resp.Body.Close()
ocspResponse, err := ocsp.ParseResponse(body, nil)
if err != nil {
fmt.Println("Could not parse OCSP response ", err)
os.Exit(1)
}
fmt.Println(ocspResponse.Status == ocsp.Good)
fmt.Println(ocspResponse.Status == ocsp.Revoked)
fmt.Println(ocspResponse.RevokedAt)
}
```
EST
The core mechanism to obtain valid certificates for your devices is using the enrollment process described by the EST protocol. EST or Enrollment over Secure Transport establishes a set of standardized endpoints. The following table sums up all endpoints defined by the EST protocol and wether or not are supported by the current implementation.
| Operation | Operation Path | Required by RFC7030 | Supported | | ------------------------------- | ------------------------------------------ | ------------------- | ---------------- | | Distribution of CA Certificates | /api/devmanager/.well-known/cacerts | | | | Enrollment of Clients | /api/devmanager/.well-known/simpleenroll | | | | Re-enrollment of Clients | /api/devmanager/.well-known/simplereenroll | | | | Full CMC | /api/devmanager/.well-known/fullcmc | | | | Server-Side Key Generation | /api/devmanager/.well-known/serverkeygen | | | | CSR Attributes | /api/devmanager/.well-known/csrattrs | | |
Distribution of CA Certificates
Install GlobalSign Est Client
```
go install github.com/globalsign/est/cmd/estclient@latest
```
Define the DOMAIN as well as the
```
export DOMAIN=dev.lamassu.io
```
Obtain the Root certificate used by the server
```
openssl s_client -connect $DOMAIN:443 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > root-ca.pem
```
Obtaining CAs certificates
```
estclient cacerts -server $DOMAIN/api/devmanager -explicit root-ca.pem -out cacerts.pem
```
```go
package main
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"github.com/lamassuiot/lamassuiot/pkg/est/client"
)
func main() {
estServerAddr := "dev.lamassu.io/api/devmanager"
servercrt := "server.crt"
clientcrt := "dms.crt"
clientkey := "dms.key"
caCert, err := ioutil.ReadFile(servercrt)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
certContent, err := ioutil.ReadFile(clientcrt)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cpb, _ := pem.Decode(certContent)
crt, err := x509.ParseCertificate(cpb.Bytes)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
key, err := ioutil.ReadFile(clientkey)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
estClient, err := client.NewLamassuEstClient(estServerAddr, caCertPool, crt, key, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cas, err := estClient.CACerts(context.Background())
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
```
Define the DOMAIN as well as the
```
export DOMAIN=dev.lamassu.io
```
Obtain the Root certificate used by the server
```
openssl s_client -connect $DOMAIN:443 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > root-ca.pem
```
Obtaining CAs certificates
```
curl https://$DOMAIN/api/devmanager/.well-known/est/cacerts -o cacerts.p7 --cacert root-ca.pem
openssl base64 -d -in cacerts.p7 | openssl pkcs7 -inform DER -outform PEM -print_certs -out cacerts.pem
```
Enrollment of Devices
Install GlobalSign Est Client
```
go install github.com/globalsign/est/cmd/estclient@latest
```
Define environment variables as well as the
```
export DOMAIN=dev.lamassu.io
export CA_NAME=Test-CA
export DEVICE_ID=$(uuidgen)
export DMS_CERT=dms.crt
export DMS_KEY=dms.key
```
!!! note
The name of the CA has to be that of a CA that has the DMS as Authorized_CAs.
Obtain the Root certificate used by the server
```
openssl s_client -connect $DOMAIN:443 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > root-ca.pem
```
Enrolling with an existing private key
```
openssl genrsa 4096 > key.pem
estclient csr -key key.pem -cn $DEVICE_ID -out csr.pem
estclient enroll -server $DOMAIN/api/devmanager -explicit root-ca.pem -csr csr.pem -aps $CA_NAME -key $DMS_KEY -certs $DMS_CERT -out cert.pem
```
```go
package main
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"github.com/lamassuiot/lamassuiot/pkg/est/client"
)
func main() {
estServerAddr := "dev.lamassu.io/api/devmanager"
servercrt := "server.crt"
dmscrt := "dms.crt"
dmskey := "dms.key"
devicecsr := "device.csr"
devicecrt:="device.crt"
ca_name := "Test-CA"
caCert, err := ioutil.ReadFile(servercrt)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
certContent, err := ioutil.ReadFile(dmscrt)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cpb, _ := pem.Decode(certContent)
crt, err := x509.ParseCertificate(cpb.Bytes)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
certContent, err = ioutil.ReadFile(devicecsr)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cpb, _ = pem.Decode(certContent)
csr, err := x509.ParseCertificateRequest(cpb.Bytes)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
key, err := ioutil.ReadFile(dmskey)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
estClient, err := client.NewLamassuEstClient(estServerAddr, caCertPool, crt, key, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cert, err := estClient.Enroll(context.Background(), ca_name, csr)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
b := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
certPEM := pem.EncodeToMemory(&b)
ioutil.WriteFile(devicecrt, certPEM, 0777)
}
```
Define environment variables as well as the
```
export DOMAIN=dev.lamassu.io
export CA_NAME=Test-CA
export DEVICE_ID=$(uuidgen)
export DMS_CERT=dms.crt
export DMS_KEY=dms.key
```
!!! note
The name of the CA has to be that of a CA that has the DMS as Authorized_CAs.
Obtain the Root certificate used by the server
```
openssl s_client -connect $DOMAIN:443 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > root-ca.pem
```
Create device CSR and private Key
```
openssl req -new -newkey rsa:2048 -nodes -keyout device.key -out device.csr -subj "/CN=$DEVICE_ID"
sed '/CERTIFICATE/d' device.csr > device_enroll.csr
```
Enrolling with an existing private key
```
curl https://$DOMAIN/api/devmanager/.well-known/est/$CA_NAME/simpleenroll --cert $DMS_CRT --key $DMS_KEY -s -o cert.p7 --cacert root-ca.pem --data-binary @device_enroll.csr -H "Content-Type: application/pkcs10"
openssl base64 -d -in cert.p7 | openssl pkcs7 -inform DER -outform PEM -print_certs -out cert.pem
```
Re-enrollment of Devices
=== "GlobalSign" Install GlobalSign Est Client
go install github.com/globalsign/est/cmd/estclient@latest
Define environment variables as well as the
```
export DOMAIN=dev.lamassu.io
export DEVICE_CRT=device.crt
export DEVICE_KEY=device.key
```
Obtain the Root certificate used by the server
```
openssl s_client -connect $DOMAIN:443 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > root-ca.pem
```
Reenrolling
```
estclient reenroll -server $DOMAIN/api/devmanager -explicit root-ca.pem -key $DEVICE_KEY -certs $DEVICE_CRT -out newcert.pem
```
```go
package main
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"github.com/lamassuiot/lamassuiot/pkg/est/client"
)
func main() {
estServerAddr := "dev.lamassu.io/api/devmanager"
servercrt := "server.crt"
devicecrt := "device.crt"
devicekey := "device.key"
devicecsr := "device.csr"
caCert, err := ioutil.ReadFile(servercrt)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
certContent, err := ioutil.ReadFile(devicecrt)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cpb, _ := pem.Decode(certContent)
crt, err := x509.ParseCertificate(cpb.Bytes)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
certContent, err = ioutil.ReadFile(devicecsr)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cpb, _ = pem.Decode(certContent)
csr, err := x509.ParseCertificateRequest(cpb.Bytes)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
key, err := ioutil.ReadFile(devicekey)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
estClient, err := client.NewLamassuEstClient(estServerAddr, caCertPool, crt, key, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cert, err := estClient.Reenroll(context.Background(), csr)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
b := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
certPEM := pem.EncodeToMemory(&b)
ioutil.WriteFile(devicecrt, certPEM, 0777)
}
```
Define environment variables as well as the
```
export DOMAIN=dev.lamassu.io
export DEVICE_CRT=device.crt
export DEVICE_KEY=device.key
exprot DEVICE_CSR=device.csr
```
Obtain the Root certificate used by the server
```
openssl s_client -connect $DOMAIN:443 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > root-ca.pem
```
Create device enroll CSR
```
sed '/CERTIFICATE/d' device.csr > device_enroll.csr
```
Reenrolling
```
curl https://$DOMAIN/api/devmanager/.well-known/est/simplereenroll --cert $DEVICE_CRT --key $DEVICE_KEY -s -o newcert.p7 --cacert root-ca.pem --data-binary @device_enroll.csr -H "Content-Type: application/pkcs10"
openssl base64 -d -in newcert.p7 | openssl pkcs7 -inform DER -outform PEM -print_certs -out $DEVICE_CRT
```
Server Key Generation of Devices
=== "GlobalSign" Install GlobalSign Est Client
go install github.com/globalsign/est/cmd/estclient@latest
Define environment variables as well as the
```
export DOMAIN=dev.lamassu.io
export CA_NAME=Test-CA
export DEVICE_ID=$(uuidgen)
export DMS_CERT=dms.crt
export DMS_KEY=dms.key
```
!!! note
The name of the CA has to be that of a CA that has the DMS as Authorized_CAs.
Obtain the Root certificate used by the server
```
openssl s_client -connect $DOMAIN:443 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > root-ca.pem
```
Enrolling with a server-generated private key
```
openssl genrsa 4096 > key.pem
estclient csr -key key.pem -cn $DEVICE_ID -out csr.pem
estclient serverkeygen -server $DOMAIN/api/devmanager -explicit root-ca.pem -csr csr.pem -aps $CA_NAME -key $DMS_KEY -certs $DMS_CERT -out device.crt -keyout device.key
```
```go
package main
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"github.com/lamassuiot/lamassuiot/pkg/est/client"
)
func main() {
estServerAddr := "dev.lamassu.io/api/devmanager"
servercrt := "server.crt"
dmscrt := "dms.crt"
dmskey := "dms.key"
devicecrt := "device.crt"
devicekey := "device.key"
devicecsr := "device.csr"
ca_name := "Test-CA"
caCert, err := ioutil.ReadFile(servercrt)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
certContent, err := ioutil.ReadFile(dmscrt)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cpb, _ := pem.Decode(certContent)
crt, err := x509.ParseCertificate(cpb.Bytes)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
certContent, err = ioutil.ReadFile(devicecsr)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cpb, _ = pem.Decode(certContent)
csr, err := x509.ParseCertificateRequest(cpb.Bytes)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
key, err := ioutil.ReadFile(dmskey)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
estClient, err := client.NewLamassuEstClient(estServerAddr, caCertPool, crt, key, nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cert, key, err := estClient.ServerKeyGen(context.Background(), ca_name, csr)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
b := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
certPEM := pem.EncodeToMemory(&b)
ioutil.WriteFile(devicecrt, certPEM, 0777)
b = pem.Block{Type: "PRIVATE KEY", Bytes: key}
keyPEM := pem.EncodeToMemory(&b)
ioutil.WriteFile(devicekey, keyPEM, 0777)
}
```
=== "Curl" Define environment variables as well as the
```
export DOMAIN=dev.lamassu.io
export CA_NAME=Test-CA
export DEVICE_ID=$(uuidgen)
export DMS_CERT=dms.crt
export DMS_KEY=dms.key
```
!!! note
The name of the CA has to be that of a CA that has the DMS as Authorized_CAs.
Obtain the Root certificate used by the server
```
openssl s_client -connect $DOMAIN:443 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > root-ca.pem
```
Create device CSR and private Key
```
openssl req -new -newkey rsa:2048 -nodes -keyout device.key -out device.csr -subj "/CN=$DEVICE_ID"
sed '/CERTIFICATE/d' device.csr > device_enroll.csr
```
Enrolling with a server-generated private key
```
curl https://$DOMAIN/api/devmanager/.well-known/est/$CA_NAME/serverkeygen --cert $DMS_CERT --key $DMS_KEY -s -o cert.p7 --cacert root-ca.pem --data-binary @device_enroll.csr -H "Content-Type: application/pkcs10"
cat cert.p7 | sed -ne '/application\/pkcs7-mime/,/-estServerKeyGenBoundary/p' | sed '/-/d' > crt.p7
openssl base64 -d -in crt.p7 | openssl pkcs7 -inform DER -outform PEM -print_certs -out device.pem
```