Skip to content

Commit

Permalink
NOISSUE - Fix OCSP with SDK (#52)
Browse files Browse the repository at this point in the history
* Fix ocsp using sdk

Signed-off-by: nyagamunene <[email protected]>

* Update mocks

Signed-off-by: nyagamunene <[email protected]>

* Address comments

Signed-off-by: nyagamunene <[email protected]>

---------

Signed-off-by: nyagamunene <[email protected]>
  • Loading branch information
nyagamunene authored Nov 22, 2024
1 parent bdaaa5f commit 2a4de1e
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 33 deletions.
1 change: 1 addition & 0 deletions api/http/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
const (
// ContentType represents JSON content type.
ContentType = "application/json"
OCSPType = "application/ocsp-response"
)

// Response contains HTTP response specific methods.
Expand Down
33 changes: 32 additions & 1 deletion api/http/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"archive/zip"
"bytes"
"context"
"encoding/asn1"
"encoding/json"
"fmt"
"io"
Expand All @@ -24,6 +25,8 @@ import (
"golang.org/x/crypto/ocsp"
)

var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1})

const (
offsetKey = "offset"
limitKey = "limit"
Expand All @@ -37,6 +40,16 @@ const (
defType = 1
)

type responseASN1 struct {
Status asn1.Enumerated
Response responseBytes `asn1:"explicit,tag:0,optional"`
}

type responseBytes struct {
ResponseType asn1.ObjectIdentifier
Response []byte
}

// MakeHandler returns a HTTP handler for API endpoints.
func MakeHandler(svc certs.Service, logger *slog.Logger, instanceID string) http.Handler {
opts := []kithttp.ServerOption{
Expand Down Expand Up @@ -267,12 +280,30 @@ func EncodeResponse(_ context.Context, w http.ResponseWriter, response interface

func encodeOSCPResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
res := response.(ocspRes)
if res.template.Certificate == nil {
ocspRes, err := asn1.Marshal(responseASN1{
Status: asn1.Enumerated(ocsp.Malformed),
Response: responseBytes{
ResponseType: idPKIXOCSPBasic,
},
})
if err != nil {
return err
}

w.Header().Set("Content-Type", OCSPType)
if _, err := w.Write(ocspRes); err != nil {
return err
}

return err
}

ocspRes, err := ocsp.CreateResponse(res.issuerCert, res.template.Certificate, res.template, res.signer)
if err != nil {
return err
}
w.Header().Set("Content-Type", "application/ocsp-response")
w.Header().Set("Content-Type", OCSPType)
if _, err := w.Write(ocspRes); err != nil {
return err
}
Expand Down
21 changes: 18 additions & 3 deletions cli/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package cli

import (
"encoding/json"
"os"

ctxsdk "github.com/absmach/certs/sdk"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -105,15 +106,29 @@ var cmdCerts = []cobra.Command{
},
},
{
Use: "ocsp <serial_number> ",
Use: "ocsp <serial_number_or_certificate_path>",
Short: "OCSP",
Long: `OCSP for a given serial number.`,
Long: `OCSP for a given serial number or certificate.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
logUsageCmd(*cmd, cmd.Use)
return
}
response, err := sdk.OCSP(args[0])

var serialNumber, certContent string

if _, statErr := os.Stat(args[0]); statErr == nil {
certBytes, err := os.ReadFile(args[0])
if err != nil {
logErrorCmd(*cmd, err)
return
}
certContent = string(certBytes)
} else {
serialNumber = args[0]
}

response, err := sdk.OCSP(serialNumber, certContent)
if err != nil {
logErrorCmd(*cmd, err)
return
Expand Down
39 changes: 18 additions & 21 deletions sdk/mocks/sdk.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

114 changes: 106 additions & 8 deletions sdk/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ package sdk
import (
"archive/zip"
"bytes"
"crypto"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"log"
"math/big"
"net/http"
"net/url"
"strconv"
Expand All @@ -24,6 +28,7 @@ import (
const (
certsEndpoint = "certs"
issueCertEndpoint = "certs/issue"
emptyOCSPbody = 22
)

const (
Expand All @@ -40,6 +45,35 @@ const (
// ContentType represents all possible content types.
type ContentType string

type CertStatus int

const (
Valid CertStatus = iota
Revoked
Unknown
)

const (
valid = "Valid"
revoked = "Revoked"
unknown = "Unknown"
)

func (c CertStatus) String() string {
switch c {
case Valid:
return valid
case Revoked:
return revoked
default:
return unknown
}
}

func (c CertStatus) MarshalJSON() ([]byte, error) {
return json.Marshal(c.String())
}

type PageMetadata struct {
Total uint64 `json:"total,omitempty"`
Offset uint64 `json:"offset,omitempty"`
Expand Down Expand Up @@ -105,6 +139,15 @@ type CertificateBundle struct {
PrivateKey []byte `json:"private_key"`
}

type OCSPResponse struct {
Status CertStatus `json:"status"`
SerialNumber *big.Int `json:"serial_number"`
RevokedAt *time.Time `json:"revoked_at,omitempty"`
ProducedAt *time.Time `json:"produced_at,omitempty"`
Certificate []byte `json:"certificate,omitempty"`
IssuerHash string `json:"issuer_hash,omitempty"`
}

type SDK interface {
// IssueCert issues a certificate for a thing required for mTLS.
//
Expand Down Expand Up @@ -165,9 +208,9 @@ type SDK interface {
// OCSP checks the revocation status of a certificate
//
// example:
// response, _ := sdk.OCSP("serialNumber")
// response, _ := sdk.OCSP("serialNumber", "")
// fmt.Println(response)
OCSP(serialNumber string) (*ocsp.Response, errors.SDKError)
OCSP(serialNumber, cert string) (OCSPResponse, errors.SDKError)

// ViewCA views the signing certificate
//
Expand Down Expand Up @@ -318,18 +361,73 @@ func (sdk mgSDK) RetrieveCertDownloadToken(serialNumber string) (Token, errors.S
return tk, nil
}

func (sdk mgSDK) OCSP(serialNumber string) (*ocsp.Response, errors.SDKError) {
func (sdk mgSDK) OCSP(serialNumber, cert string) (OCSPResponse, errors.SDKError) {
var sn *big.Int
var ok bool

if serialNumber == "" && cert == "" {
return OCSPResponse{}, errors.NewSDKError(errors.New("either serial number or certificate must be provided"))
}

if serialNumber != "" {
sn, ok = new(big.Int).SetString(serialNumber, 10)
if !ok {
return OCSPResponse{}, errors.NewSDKError(errors.New("invalid serial number"))
}
}

if cert != "" {
block, _ := pem.Decode([]byte(cert))
if block == nil {
return OCSPResponse{}, errors.NewSDKError(errors.New("failed to decode PEM block"))
}

parsedCert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return OCSPResponse{}, errors.NewSDKError(err)
}
sn = parsedCert.SerialNumber
}

req := ocsp.Request{
SerialNumber: sn,
HashAlgorithm: crypto.SHA256,
IssuerNameHash: nil,
IssuerKeyHash: nil,
}

requestBody, err := req.Marshal()
if err != nil {
return OCSPResponse{}, errors.NewSDKError(err)
}

url := fmt.Sprintf("%s/%s/ocsp", sdk.certsURL, certsEndpoint)
requestBody := []byte(serialNumber)
_, body, sdkerr := sdk.processRequest(http.MethodPost, url, requestBody, nil, http.StatusOK)
if sdkerr != nil {
return &ocsp.Response{}, sdkerr
return OCSPResponse{}, sdkerr
}
ocspResp, err := ocsp.ParseResponse(body, nil)

if len(body) == emptyOCSPbody {
return OCSPResponse{
Status: CertStatus(Unknown),
SerialNumber: sn,
}, nil
}
res, err := ocsp.ParseResponse(body, nil)
if err != nil {
return &ocsp.Response{}, errors.NewSDKError(err)
return OCSPResponse{}, errors.NewSDKError(err)
}

resp := OCSPResponse{
Status: CertStatus(res.Status),
SerialNumber: res.SerialNumber,
Certificate: res.Certificate.Raw,
RevokedAt: &res.RevokedAt,
IssuerHash: res.IssuerHash.String(),
ProducedAt: &res.ProducedAt,
}
return ocspResp, nil

return resp, nil
}

func (sdk mgSDK) ViewCA(token string) (Certificate, errors.SDKError) {
Expand Down

0 comments on commit 2a4de1e

Please sign in to comment.